<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Posts on ilyess</title>
    <link>https://ilyess.cc/posts/</link>
    <description>Recent content in Posts on ilyess</description>
    <image>
      <title>ilyess</title>
      <url>https://ilyess.cc/%3Clink%20or%20path%20of%20image%20for%20opengraph,%20twitter-cards%3E</url>
      <link>https://ilyess.cc/%3Clink%20or%20path%20of%20image%20for%20opengraph,%20twitter-cards%3E</link>
    </image>
    <generator>Hugo -- gohugo.io</generator>
    <language>en</language>
    <lastBuildDate>Fri, 30 Jun 2023 10:00:00 -0500</lastBuildDate><atom:link href="https://ilyess.cc/posts/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>The Four Levels of Mastodon Mastery</title>
      <link>https://ilyess.cc/posts/the-four-levels-of-mastodon-mastery/</link>
      <pubDate>Fri, 30 Jun 2023 10:00:00 -0500</pubDate>
      
      <guid>https://ilyess.cc/posts/the-four-levels-of-mastodon-mastery/</guid>
      <description>In the vast landscape of social media platforms, Mastodon has emerged as a refreshing alternative. With its decentralized nature and commitment to user privacy, Mastodon offers a unique experience that fosters community building and meaningful interactions. However, mastering Mastodon requires navigating through different levels of proficiency, especially for someone coming from a traditional, centralized platform, like Twitter. In this article, we will explore the various stages of mastery on Mastodon and uncover the possibilities that each level brings.</description>
      <content:encoded><![CDATA[<p><img loading="lazy" src="/images/mastodon-resize.webp" alt="code"  />
</p>
<p>In the vast landscape of social media platforms, Mastodon has emerged as a refreshing alternative.
With its decentralized nature and commitment to user privacy, Mastodon offers a unique experience
that fosters community building and meaningful interactions. However, mastering Mastodon requires
navigating through different levels of proficiency, especially for someone coming from a
traditional, centralized platform, like Twitter. In this article, we will explore the various stages
of mastery on Mastodon and uncover the possibilities that each level brings.</p>
<h2 id="level-1-picking-a-home">Level 1: Picking a Home</h2>
<p>As a newcomer to Mastodon, the first step is to select a server, also known as an instance. Mastodon
is built upon a federated model, meaning that it consists of multiple interconnected <a href="https://joinmastodon.org/servers">servers</a> rather
than one centralized platform. Each server has its own community, rules, and moderation policies.
This decentralized structure empowers users with the ability to choose an instance that aligns with
their interests and values.</p>
<p>During this stage, it&rsquo;s essential to research and understand the ethos of different instances. Some
instances cater to specific topics or themes, such as art, technology, or activism, while others
focus on fostering a safe and inclusive environment. By picking the right server, you can easily
connect with like-minded individuals and immerse yourself in communities that resonate with your
passions.</p>
<p>It&rsquo;s worth noting that the decision of picking a server is not permanent or binding. Mastodon allows
users to seamlessly move their account from one server to another. If you find that your chosen
server doesn&rsquo;t meet your expectations or you wish to explore new communities, you can easily migrate
your account to a different server without losing your followers or the people you follow<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>. This
flexibility ensures that even if you initially choose a server that doesn&rsquo;t align perfectly with
your preferences, it&rsquo;s not a big deal, and you have the freedom to make adjustments as you explore
the Mastodon ecosystem.</p>
<p>At this point, you have pretty much everything you need to get started on Mastodon. Using the
official website or mobile app, you can set up your profile, post your first toot<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>, which I
recommend it be an <a href="https://mastodon.social/tags/introduction">introduction</a> post, and start exploring the platform. However, if you&rsquo;re looking
to take your Mastodon experience to the next level, you can consider other third-party apps that
offer additional features and customization options. We&rsquo;ll explore this in the next section.</p>
<h2 id="level-2-experimenting-with-different-apps">Level 2: Experimenting with different apps</h2>
<p>There are many <a href="https://joinmastodon.org/apps">third-party apps</a> available for Mastodon, like <a href="https://tusky.app">Tusky</a> and Tooot on Android, or <a href="https://github.com/Dimillian/IceCubesApp">iceCubes</a>
and iMast on iOS, that offer intuitive interfaces and a range of features that could enhance your
experience. Experimenting with different apps allows you to find one that suits your preferences
in terms of user interface, customization options, and additional functionalities.</p>
<p>As you delve into different apps, you&rsquo;ll discover features like customizable timelines, advanced
search options, and the ability to mute or block specific users or keywords. By familiarizing
yourself with these features, you can tailor your Mastodon experience to suit your individual needs
and preferences.</p>
<p>I personally haven&rsquo;t evolved past this level, and I&rsquo;m sure most people won&rsquo;t either. However, if
you&rsquo;re looking to take your Mastodon experience even further and gain more control over moderation
policies, server versions, data, and more; you can consider hosting your own instance which we&rsquo;ll
turn to in the next section.</p>
<h2 id="level-3-hosting-your-own-single-user-instance">Level 3: Hosting Your Own Single-User Instance</h2>
<p>At this stage, you have the opportunity to take complete control over your Mastodon experience by
hosting your own single-user instance which allows you to personalize your Mastodon environment to
your exact specifications.</p>
<p>By hosting your own instance as a single user, you gain unparalleled freedom and flexibility. You
become the sole administrator of your instance, allowing you to define the rules, moderation
policies, and themes according to your preferences. With this level of control, you can curate a
space that truly reflects your values and interests.</p>
<p>One of the major advantages of hosting your own single-user instance is the ability to customize the
appearance and functionality to suit your needs. You can tailor the instance&rsquo;s design, branding, and
features to align perfectly with your personal style and requirements. Whether it&rsquo;s the color
scheme, layout, or additional functionalities, you have the power to shape your Mastodon experience
exactly the way you envision it.</p>
<p>Hosting your own instance also eliminates any concerns about relying on external servers. You have
full autonomy over the server maintenance, ensuring optimal performance, security, and data privacy.
It goes without saying that hosting your own instance does require technical knowledge and
resources, as you will be responsible for server setup, maintenance, and regular updates.</p>
<p>It&rsquo;s worth clarifying that being the sole user of your instance doesn&rsquo;t prevent you from engaging
with others, and building your online presence. While your instance doesn&rsquo;t aim to build a
community, you can still connect with other users as if you were all on the same server.</p>
<p>Now, you don&rsquo;t have to stop here. While hosting your own single-user instance is more than what
most people will ever need, you can take it a step further and open your instance for registration
to welcome other users. We&rsquo;ll explore this in the next section.</p>
<h2 id="level-4-fostering-a-community">Level 4: Fostering a Community</h2>
<p>As you continue to explore Mastodon and expand your reach, you may consider opening registration for
users on your instance, with the intention of fostering a community around shared interests and
values.</p>
<p>By opening registration, you provide an opportunity for people who resonate with your content or the
community you aim to build to join your instance and participate. This step allows you to create a
space where individuals with similar passions can connect, collaborate, and engage in meaningful
discussions.</p>
<p>As the administrator of your instance, you have the power to shape the community by defining the
rules, themes, and moderation policies. Encourage respectful and constructive interactions, foster a
supportive environment, and create spaces for people to share their thoughts, ideas, and creations.</p>
<p>Additionally, by welcoming others to your instance, you not only create a space for like-minded
individuals to connect but also contribute to the decentralization and scalability of the Mastodon
network. In a decentralized ecosystem like Mastodon, distributing the user load across multiple
instances is crucial to ensure stability, performance, and avoid instances becoming overcrowded.
Opening your instance to newcomers allows for a healthier distribution of users across the network,
preventing the concentration of a large user base on a few instances. This decentralization is key
to maintaining a vibrant and sustainable Mastodon community.</p>
<h2 id="wrap-up">Wrap Up</h2>
<p>Mastering Mastodon is an exciting journey that unfolds across different stages of proficiency. From
selecting the right server to exploring various apps, hosting your own single-user instance, and
potentially fostering a community, each level offers unique opportunities to shape your Mastodon
experience. Embrace the flexibility, connect with others, and discover the diverse communities
within Mastodon. Whether you&rsquo;re seeking an alternative to traditional social media or championing
privacy and inclusivity, Mastodon has something to offer at every step of your personal mastery. So,
embark on this journey, make meaningful connections, and let Mastodon become your digital haven.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Note that posts, on the other hand, are not transferable between servers. As it stands today,
if you move your account to a new server, your posts will not be migrated.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>A toot is the Mastodon equivalent of a tweet on Twitter or a post on Facebook.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    
    <item>
      <title>Your Blog Does Not Need a Database</title>
      <link>https://ilyess.cc/posts/your-blog-does-not-need-a-database/</link>
      <pubDate>Tue, 14 Feb 2023 20:00:00 -0400</pubDate>
      
      <guid>https://ilyess.cc/posts/your-blog-does-not-need-a-database/</guid>
      <description>In the present digital epoch, it has become imperative for companies with an online presence to host a blog which is nothing more than a compendium of articles or posts, typically organized chronologically. This is not limited to companies obviously and anyone can, and is even highly encouraged to, run a personal blog. If you&amp;rsquo;re one of the fortunate who own a personal blog and are currently relying on a database to power it, you may be under the impression that it&amp;rsquo;s necessary for your blog to function properly.</description>
      <content:encoded><![CDATA[<p><img loading="lazy" src="/images/code.webp" alt="code"  />
</p>
<p>In the present digital epoch, it has become imperative for companies with an online presence to host
a blog which is nothing more than a compendium of articles or posts, typically organized
chronologically. This is not limited to companies obviously and anyone can, and is even highly
encouraged to, run a personal blog. If you&rsquo;re one of the fortunate who own a personal blog and are
currently relying on a database to power it, you may be under the impression that it&rsquo;s necessary for
your blog to function properly. Wrong! In this article I&rsquo;m going to explain why that&rsquo;s not the case
and why your blog doesn&rsquo;t actually need a database. I will even go further and suggest that your
blog <em>should not</em> use a database. While databases can be useful, if not mandatory, for certain types
of websites, blogs are definitely not one of them.</p>
<p>One of the main reasons why blogs don&rsquo;t need a database is that they are typically static websites.
This means that the content on the website does not change frequently and isn&rsquo;t customized for any
particular visitor, and the website does not require any user interaction. In such a case, a
database would be unnecessary as the information on the website can be stored in the form of HTML
and CSS files.</p>
<p>Before going further, if your website has any dynamic functionality like user
registration and login, or first party comment support, you&rsquo;re out of luck and there&rsquo;s no way around
using a database; unless you&rsquo;re feeling adventurous enough to delegate those functionalities to
third party providers and hook your website onto them solely through the frontend. This adds a bit
of complexity and calls for some technical knowledge though, which voids some of the benefits of
static websites. Maybe you don&rsquo;t <em>need</em> those dynamic functionalities to begin with and might be
better off without them. Food for thought.</p>
<h2 id="the-old-school">The Old School</h2>
<p>The traditional method of running a blog is by utilizing a database to store the articles and a
server-side script that dynamically generates the pages upon a user&rsquo;s request. This method, commonly
referred to as a &ldquo;dynamic&rdquo; approach, has been the prevalent norm for a considerable duration.
However, this has several drawbacks.</p>
<p>First, the energy consumption is a paramount concern. Databases necessitate a substantial
computational capacity, and oftentimes, they are run on servers that consume copious amounts of
energy. This energy consumption leads to a substantial carbon footprint, thereby exacerbating
climate change. Furthermore, the servers that host these databases are usually situated in data
centers, which consume substantial amounts of energy to cool the servers and keep them operational.
In addition, databases are frequently over-provisioned and underutilized, resulting in the squander
of resources. This is a direct result of the fact that a database must be able to handle the peak
load, even though the actual load is frequently much lower. This is a significant waste of
resources, both in terms of energy and hardware.</p>
<p>Another drawback of using a database is the cost. Hosting a dynamic blog requires a powerful server,
as well as a database. This can be expensive, especially for personal blogs which are not typically
meant to generate revenue. Additionally, the more traffic a blog receives, the more powerful the
server must be, and the more resources are required to handle the traffic. This can make it
difficult for a blog to scale, and can also be a significant barrier to entry for those who are just
starting out and do not have a lot of resources.</p>
<p>The third drawback of using a database is the slower page load times. When a user requests a page on
a dynamic blog, the server must first query the database to retrieve the data, and then execute the
server-side scripts to generate the page. This process takes time, and the more traffic a blog
receives, the longer it takes to generate the pages. This can lead to a poor user experience, and
can also negatively impact the blog&rsquo;s search engine rankings.</p>
<h2 id="the-hot-new-thing">The Hot New Thing</h2>
<p>An alternative approach is to use a statically generated blog, which pre-generates all the pages and
serves them as plain HTML and CSS files. This approach has several advantages over using a database
besides the obvious benefits of not carrying the aforementioned drawbacks.</p>
<p>First, since the pages are pre-generated, they can be served directly by a web server without the
need for any dynamic processing. This means that the hosting costs are lower, as you don&rsquo;t need a
powerful server or a database. Additionally, the static pages can be served directly by a Content
Delivery Network (CDN), which can further reduce the hosting costs, and are often cached by the
browser which also improves the page load times.</p>
<p>Another advantage of using a statically generated blog is the ability to generate pages offline.
With a dynamic blog, the pages are generated on the fly, which means that the server must be online
and accessible in order for the website to function. With a statically generated blog, however, the
pages can be generated offline, and then uploaded to the web server. This can be useful for testing
and brings a high degree of portability. Given that the website is nothing but static files, you can
almost instantly move your website from one hosting provider to another.</p>
<p>The third advantage of using a statically generated blog is the increased security. With a dynamic
blog, there is always a risk that the server-side scripts or the database could be hacked, which
could potentially lead to sensitive information being compromised. With a statically generated blog,
on the other hand, there is no server-side script or database to hack, which reduces the risk of a
security breach.</p>
<h2 id="i-dont-want-to-deal-with-html-and-css">I Don&rsquo;t Want To Deal With HTML And CSS</h2>
<p>If the thought of having to manually create and manipulate HTML and CSS files makes you sweat,
don&rsquo;t you worry. Using a statically generated website doesn&rsquo;t mean that you need to become an
proficient frontend developer. There are tools, so called static site generators (SSG), that allow
you to create your blog using simple text files, such as Markdown, and then generate the necessary
HTML and CSS files. This means that you can exclusively focus on creating content and customizing
the design of your blog, by leveraging off-the-shelf themes.</p>
<p>One of the most popular SSG is <a href="https://jekyllrb.com">Jekyll</a>, which is written in Ruby. Jekyll takes your content, written
in Markdown or Textile, and uses layouts to create a static website. Jekyll also supports data
files, which can be used to store data in a structured format, such as YAML or JSON, that can be
accessed in your templates.</p>
<p>Another popular SSG is <a href="https://gohugo.io">Hugo</a>, which is written in Go. It is a fast and flexible tool that can be used
to build a wide variety of websites, including blogs. Hugo uses markdown files to generate your
website, and it supports data files and template variables, which can be used to store and access
data in a structured format.</p>
<p>It goes without saying that there are many more static site generators to choose from. Once the
static website files are generated, they need to be hosted by a web hosting provider for the website
to go online and be accessible from the internet. Here again, there&rsquo;s no shortage of web hosting
providers on the market. In fact, many would even be happy to host your static website for free!
Providers like <a href="https://www.netlify.com">Netlify</a>, <a href="https://codeberg.page/">Codeberg Pages</a>, <a href="https://pages.github.com/">Github Pages</a>, and <a href="https://vercel.com/">Vercel</a> immediately come to mind but I&rsquo;m
sure there are plenty others.</p>
<h2 id="wrap-up">Wrap up</h2>
<p>In today&rsquo;s world, environmental sustainability is a top priority. And when it comes to blogs,
statically generated sites are the clear winner. They require less energy and computing power, are
much more secure than their dynamic counterparts, and are much easier to maintain. While I doubt
that we can solve all our climate problems by making blogs static, I do believe that every bit of
contribution towards that goal helps, however minute it seems. So if you&rsquo;re looking to reduce your
environmental impact and create a secure, cost-effective blog, be sure to go static!</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>My Journey With Note Apps</title>
      <link>https://ilyess.cc/posts/my-journey-with-note-apps/</link>
      <pubDate>Thu, 19 Jan 2023 20:00:00 -0400</pubDate>
      
      <guid>https://ilyess.cc/posts/my-journey-with-note-apps/</guid>
      <description>Everybody needs to take notes, may it be for school, to have a handy grocery list in the store, or to keep a log of business expenses for accounting purposes. Some people use physical notebooks, some pick up whatever napkin happens to be in the immediate vicinity and roll with it for the day, and others, like myself, prefer to leverage digital notes. In this article, I&amp;rsquo;ll take you through my journey with note taking apps.</description>
      <content:encoded><![CDATA[<p><img loading="lazy" src="/images/notes.webp" alt="notes"  />
</p>
<p>Everybody needs to take notes, may it be for school, to have a handy grocery list in the store, or
to keep a log of business expenses for accounting purposes. Some people use physical notebooks, some
pick up whatever napkin happens to be in the immediate vicinity and roll with it for the day, and
others, like myself, prefer to leverage digital notes. In this article, I&rsquo;ll take you through my
journey with note taking apps. We&rsquo;ll go over my personal definition of a &ldquo;good&rdquo; note taking app,
explore some of the options I used and the challenges I faced, and finally land on the solution I
ended up adopting with a couple of learnings that emerged along the way.</p>
<h2 id="the-beginning">The Beginning</h2>
<p>My journey with digital note taking started with whatever default notes app happened to be on my
phone. At first, I didn&rsquo;t have any particular framework for note taking; it was just a random
collection of digital post-it stickers. Similar story on the desktop, a structureless heterogeneous
cluster of text files scattered all over the place.</p>
<p>Then, I started taking notes more seriously (pun intended) to capture knowledge, such as book
summaries and highlights, things I learn from videos and documentaries, new vocabulary I come
across, random ideas or questions I want to get answers to, and more. Before you know it, my notes
catalogue started getting a bit heavy and it was apparent that I needed some sort of framework to
better tag and organize all that knowledge. At the time, <a href="https://evernote.com">Evernote</a> was the new kid on the block and
it had some attractive features, so without putting much thought into it, I created an account and
poured all my notes over to it.</p>
<p>I used Evernote for a long time, longer than I would want to admit. But one day, shortly after I&rsquo;d
begun getting serious about my online privacy, I had a &ldquo;Oh shit!&rdquo; moment that I still remember to
this day.</p>
<h2 id="the-rabbit-hole">The Rabbit Hole</h2>
<p>I geared up and I ventured down the rabbit hole of privacy-friendly, and open-source, note taking
apps. I set a few criteria to guide my expedition:</p>
<ol>
<li>The service has to be free of charge, or offer a free plan;</li>
<li>It has to be offline and/or support end-to-end encrypted backup and/or sync; and</li>
<li>It must respect user privacy, so no tracking, ads, or telemetry.</li>
</ol>
<p>If there&rsquo;s one thing I would never accept, it would be having my notes up in the cloud in the
clear<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>, may that be temporarily for synchronization or permanently for backup. My notes hold so
much information about me, from future plans, to areas of interest in life, to just general
knowledge. I bet that even my IQ could be deduced with a reasonable degree of certainty using the
right AI model. Now that I think of it, I would definitely see myself in the future training a model
on my notes and delegating some decision making to it: a tailored virtual assistant of sort. I&rsquo;m no
AI expert and this does seem far-fetched today, but I wouldn&rsquo;t be surprised if it were a thing even
half a decade from now. Anyway, I digress.</p>
<p>During my research, <a href="https://standardnotes.com">StandardNotes</a> stood out as a good candidate with its end-to-end encrypted backup
and sync, and focus on privacy. It seemed to check all the boxes, so I gave it a shot. I created a
free account and, once again, migrated all my notes off of Evernote into my StandardNotes account. I
have to admit that this immediately felt like a downgrade; the feature set of StandardNotes&rsquo; free
tier is rudimentary at best. But in the name of privacy, I sucked it up and worked around the
limitations for a while. Thinking back, I realize that this was more of a &ldquo;stop the bleeding&rdquo; step
than a permanent solution. Case in point, I never actually stopped exploring the free and open
source note taking space for better options. Then, I came across <a href="https://joplinapp.org">Joplin</a>.</p>
<p>At first, Joplin&rsquo;s UI/UX looked out-dated, especially on mobile devices, but I was willing to be
more lenient given that it&rsquo;s a free and open source software maintained by a handful of generous
people. What grabbed my attention was its self-hostable end-to-end encrypted capability. So I
downloaded a copy of the app, set up a WebDAV server to handle the sync, and migrated all my notes
over. After a few weeks of me getting used to its archaic UI, Joplin began setting itself as my
permanent note taking solution. I started tinkering with various plugins, and even looked into
making my own. All was great until the day the sync broke! My client was getting 503 errors from the
sync server which corrupted some notes and rendered them unusable. Luckily, I had an offline backup
of the affected files, so I managed to recover, albeit laboriously, from this crash. A few weeks
later, another sync crash wiped my <em>entire</em> notebook. I wasn&rsquo;t so lucky this time around, however,
and ended up permanently losing some of my newer notes. This was the straw that broke the camel&rsquo;s
back. I couldn&rsquo;t take it anymore; I had to find a better solution.</p>
<p>I decided to give StandardNotes another shot, but going with a self-hosted instance this time. This
will ensure that I have full control over my notes&rsquo; backups, and that I enjoy a more decent set of
features; or so I thought. So I migrated all my notes, once more, to my <a href="/posts/self-host-standard-notes-with-premium-extensions">StandardNotes instance</a> and
started playing around with premium themes and extensions. The fact that a simple notes service
required so many backend services, a.k.a micro-services, has never really sat well with me. I had
the feeling that this level of complexity will eventually prove too cumbersome to maintain. And
guess what? It did! After a recent update of the desktop client, my setup was brought to a halt. I
was no longer able to connect to my self-hosted instance, running an old version of the server. It
seemed like the new client version wasn&rsquo;t backward-compatible and required a backend upgrade. When I
tried to make that happen, I realized that the StandardNotes&rsquo; team overhauled their entire
architecture. At this point, I wasn&rsquo;t really down to learn the new setup, build new docker images,
and get everything back online. It felt like a lot more work than a simple notes service should call
for.</p>
<h2 id="refocusing">Refocusing</h2>
<p>I took a step back, and went back to the drawing board. A quick cost/reward analysis confirmed that
all of this friction wasn&rsquo;t worth it. I realized that I didn&rsquo;t <em>really</em> need the cross-device sync,
since the vast majority of my notes fell under 2 categories: desktop, and phone. I seldom need notes
from my phone when I&rsquo;m on the desktop, and I almost never need notes from my desktop when I&rsquo;m on my
phone. So I decided to use 2 different note taking applications: <a href="https://www.vim.org">vim</a> with <a href="https://vimwiki.github.io">vimwiki</a> for desktop notes,
and my phone&rsquo;s default notes app with cloud backup disabled. I suppose you know the drill by now: I
migrated all of my notes, yes once again, from StandardNotes to vim and my phone&rsquo;s default note app,
hoping that this was the last time I perform this dance.</p>
<p>I&rsquo;ve been driving this setup for weeks now and so far have a couple of takeaways. First, I&rsquo;m struck
by how pleasant it is to enter a typo&rsquo;ed keyword into a search bar and watch the powerful fuzzy
search engine instantly populate the page with extremely relevant results, the first one being the
one I had in mind most of the time. I can&rsquo;t remember a single time where this failed me. This made
me realize just how mediocre, if not completely futile, StandardNotes&rsquo; search functionality was,
particularly on mobile. Second, I never <em>actually</em> needed the cross-device sync or cloud backup for
my notes. Offline backup, and segregated notebooks work just fine, check all the boxes with regards
to privacy, and cost me practically no effort.</p>
<p>Before closing this off, I&rsquo;d like to mention a couple other note services that I considered at some
point, but wasn&rsquo;t entirely sold on so I never took them for a test drive. <a href="https://obsidian.md">Obsidian</a> is more than just
a note taking app. With its graph features and automatic note linking, it appears to be more of a
knowledge management software; way more than I actually need for my personal use. Plus, the sync is
a paid service with no option to self-host. <a href="https://logseq.com">Logseq</a> is another solution that briefly caught my
attention until I learned that it had no native sync support; the only way to sync data was to go
through Github or iCloud.</p>
<h2 id="wrap-up">Wrap up</h2>
<p>If there&rsquo;s a lesson to be drawn from this journey, it has to be that the most complex solution isn&rsquo;t
always the right one. I would argue that the simplest one turns out to be the best, more often than
not. It just took me multiple migrations, hours if not days of tinkering around with various setups,
and quite a bit of frustration wrestling with poor-UX software and dealing with lousy error
handling, to realize it.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>I consider &ldquo;in the clear&rdquo; data any encrypted data where the keys belong to or are managed by
the server. Put differently, in this context, if it&rsquo;s not end-to-end encrypted, it&rsquo;s in the clear.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    
    <item>
      <title>Self Host Standard Notes With Premium Extensions</title>
      <link>https://ilyess.cc/posts/self-host-standard-notes-with-premium-extensions/</link>
      <pubDate>Sat, 18 Jun 2022 19:30:00 -0400</pubDate>
      
      <guid>https://ilyess.cc/posts/self-host-standard-notes-with-premium-extensions/</guid>
      <description>Standard Notes is an open-source note taking application available on desktop and mobile platforms that offers end-to-end encrypted synchronization. In other words, all data are encrypted on device before they&amp;rsquo;re sent to the server to propagate to other devices. I think it&amp;rsquo;s one of the best open-source note taking products out there with a free plan, at the time of this writing.
That being said, their introductory free tier leaves a lot of be desired.</description>
      <content:encoded><![CDATA[<p><a href="https://standardnotes.com">Standard Notes</a> is an open-source note taking application available on desktop
and mobile platforms that offers end-to-end encrypted synchronization. In other
words, all data are encrypted on device before they&rsquo;re sent to the server to
propagate to other devices. I think it&rsquo;s one of the best open-source note taking
products out there with a free plan, at the time of this writing.</p>
<p>That being said, their introductory free tier leaves a lot of be desired. It
could work for users with basic note taking needs but if you want to organize
your notes in nested folders, use multiple editors like Markdown and Rich text,
switch between a variety of themes, or schedule regular backups, you have to go
with a premium plan. At $12/month, or $5/month if billed yearly,
<a href="https://standardnotes.com/plans">their cheapest plan</a> is definitely at the higher end of the spectrum, especially
that the service isn&rsquo;t complex enough to warrant the hefty subscription fee in
my view. It should come at no surprise if a Standard Notes subscription is
considered a quite hard expense to justify by regular users who don&rsquo;t fiddle
with notes on a daily basis, or heavily rely on note taking in a professional
capacity.</p>
<p>Fortunately, the Standard Notes team is nice enough to open source all their
stack for anyone to self-host for personal use. On top of the obvious benefit of
enjoying all the premium features free of charge, self-hosting comes with the
advantage of full data control. The official documentation around self-hosting
published by the Standard Notes team is clear and straightforward. They put
together a docker-compose file that defines the entire stack along with a
convenient script to create configuration files, bring the stack up and down,
view logs, and more.</p>
<p>Thanks to the power of containers, you can be up and running in a matter of
minutes, unless you&rsquo;re running an ARM chip like a Raspberry Pi. Sadly, Standard
Notes don&rsquo;t provide official Docker images for ARM processors. You can either
pick one of the third-party images made available by random users, or build your
own. I tend to go with the latter option because it grants more control over the
software version packed in the image, and most importantly it obviates the
inherent security risk that comes with running untrusted Docker images<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>. In
the next section, we&rsquo;ll go over the steps to build Docker images from source
compatible with ARM processors, or any target architecture for that matter.  If
the official Docker images work on your processor architectures, feel free to
jump straight to the <a href="#premium-extensions">&ldquo;Premium extensions&rdquo;</a> section.</p>
<h2 id="building-docker-images-for-arm">Building Docker images for ARM</h2>
<p>First, we need to pull the code of the service we want to build a Docker image
for. Let&rsquo;s go with the <code>auth</code> service.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>git clone https://github.com/standardnotes/auth
</span></span></code></pre></div><p>Then, from within the <code>auth</code> folder, let&rsquo;s switch to the latest tag, or any other
target version you want to use:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>cd auth
</span></span><span style="display:flex;"><span>git checkout 1.46.1
</span></span></code></pre></div><p>If you&rsquo;re not sure what&rsquo;s the latest tag, you can either check it directly on
Github or list all available tags using:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>git tag --list
</span></span></code></pre></div><p>If you&rsquo;re building the image on the same machine that&rsquo;s going to ultimately run
the container, all you have to do is run a typical Docker build command like:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>docker build -t arm-auth:1.46.1 .
</span></span></code></pre></div><p>However, if you&rsquo;re building the image on a separate machine, perhaps
because your server doesn&rsquo;t have enough resources to carry out the build
process, you can target a specific CPU architecture using <code>buildx</code>. Here&rsquo;s what
the command would look like:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>docker buildx build --platform linux/arm/v6 -t arm-auth:1.46.1 . --load
</span></span></code></pre></div><p>Here, we&rsquo;re targeting the version 6 of ARM and tagging the image with
<code>arm-auth:1.46.1</code>. If you&rsquo;re running a different version of ARM make sure to
update the <code>--platform</code> flag accordingly.</p>
<p>As a side note, this will only work if <code>linux/arm/v6</code> is loaded in the current
<code>buildx</code> builder. To check the platforms supported by your <code>buildx</code> builder,
run:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>docker buildx ls
</span></span></code></pre></div><p>If your target platform is not currently loaded in <code>buildx</code>, try running a
<a href="https://hub.docker.com/r/linuxkit/binfmt/">binfmt</a> container like the following:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>docker run --privileged --rm linuxkit/binfmt:a17941b47f5cb262638cfb49ffc59ac5ac2bf334
</span></span></code></pre></div><p>Now that the image is built, we need to move it to the server where it&rsquo;s
supposed to run. To do so, we first need to save it in a <code>tar</code> archive using the
following command:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>docker save arm-auth:1.46.1 -o arm-auth.tar
</span></span></code></pre></div><p>Then, after transferring it to the server via <code>rsync</code>, a USB key, or whatever
other means deemed most convenient, we should load it on the server using:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>docker load -i arm-auth.tar
</span></span></code></pre></div><p>Now, if we list all Docker images using <code>docker images</code> we should see our new
<code>arm-auth</code> image. All that&rsquo;s left to do is reference this image inside the
standalone <a href="https://github.com/standardnotes/standalone/blob/main/docker-compose.yml">docker-compose file</a> provided by Standard Notes.</p>
<p>It goes without saying that all the steps we&rsquo;ve gone through in this section
would need to be repeated for each one of the official Standard Notes services,
which currently are:</p>
<ul>
<li><a href="https://github.com/standardnotes/syncing-server-js">syncing-server-js</a>;</li>
<li><a href="https://github.com/standardnotes/api-gateway">api-gateway</a>; and</li>
<li><a href="https://github.com/standardnotes/files">files</a>.</li>
</ul>
<p>On top of their own services, Standard Notes use Redis for cache and MySQL for
storage. The latter can be replaced with a MariaDB ARM image like
<a href="https://hub.docker.com/r/linuxserver/mariadb">linuxserver/mariadb</a> while the former can be used as is; it already has a Docker
image for ARM.</p>
<h2 id="premium-extensions">Premium extensions</h2>
<p>Once your self-hosted Standard Notes instance is up, you can connect to it by
pointing your Standard Notes client to your instance, under the &ldquo;Advanced&rdquo;
section of the account creation or login view. You first need to create an
account, which will be a &ldquo;Basic&rdquo; one by default, equivalent to the free tier. To
be able to use premium themes and editors you need to upgrade your account with
<a href="https://docs.standardnotes.com/self-hosting/subscriptions">a couple of commands</a>. Unfortunately, having a premium account in your
self-hosted instance doesn&rsquo;t automatically give you access to premium themes and
editors.  Standard Notes don&rsquo;t give out their official premium extensions for
free but you can load other community-built alternatives. Here are a couple of
sources to get you started:</p>
<ul>
<li><a href="https://snexts.github.io/">https://snexts.github.io/</a></li>
<li><a href="https://github.com/jonhadfield/awesome-standard-notes">https://github.com/jonhadfield/awesome-standard-notes</a></li>
</ul>
<p>To load an extension, head over to your desktop client under Preferences &gt;
General &gt; Advanced Settings. Then, enter the extension URL in the &ldquo;Install
Custom Extension&rdquo; input field and hit &ldquo;Install&rdquo;.</p>
<h2 id="wrap-up">Wrap up</h2>
<p>Self-hosting Standard Notes is strikingly straightforward, barring few extra
steps that require some Docker knowledge when working with an ARM chip. In this
article, we&rsquo;ve gone over everything needed to setup a Standard Notes server
that&rsquo;s not covered in the official documentation.</p>
<p>If you have any questions or face difficulties with the instructions laid out
above, feel free to reach out on <a href="https://mastodon.online/@ilyess">Mastodon</a>.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>The Docker image could fall out of date and lack crucial security patches;
or, more unlikely although not totally impossible, contain malware.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    
    <item>
      <title>Why I Love Firefox</title>
      <link>https://ilyess.cc/posts/why-i-love-firefox/</link>
      <pubDate>Sun, 22 May 2022 20:00:00 -0400</pubDate>
      
      <guid>https://ilyess.cc/posts/why-i-love-firefox/</guid>
      <description>Long gone are the days when websites consisted of static HTML pages styled with basic CSS, when links actually directed users to new web locations instead of calling a Javascript function. Now, websites (or should I call them web apps?) have grown so complex that browsers had no choice but to turn into operating systems. This made for a much richer user experience with so many possibilities; you can do online banking, play video games, join video conferences, shop, and even install apps, all within the same application: the browser.</description>
      <content:encoded><![CDATA[<p><img loading="lazy" src="/images/firefox.webp" alt="firefox"  />
</p>
<p>Long gone are the days when websites consisted of static HTML pages styled with
basic CSS, when links actually directed users to new web locations instead of
calling a Javascript function. Now, websites (or should I call them web apps?)
have grown so complex that browsers had no choice but to turn into operating
systems. This made for a much richer user experience with so many possibilities;
you can do online banking, play video games, join video conferences, shop, and
even install apps, all within the same application: the browser. It&rsquo;s obvious
that the more complex any software gets, the more chances there are for security
vulnerabilities to emerge, and the browser is no exception.
<a href="https://www.cvedetails.com/vulnerability-list.php?vendor_id=1224&amp;product_id=15031&amp;page=1&amp;year=2021">The surge of browser CVEs</a> we&rsquo;ve witnessed in recent years is a good testament
to that. Additionally, the more user interactions are made possible in the
browser the more attractive it becomes to marketers and companies looking for
new revenue streams. Online advertising, which is fueled by online tracking,
has become so lucrative that it&rsquo;s consistently been <a href="https://www.statista.com/statistics/1093781/distribution-of-googles-revenues-by-segment/">the top revenue category</a>
for so many big tech corporations. So, should we just throw our hands up in the
air and accept our imposed destiny? Do we have to give up our privacy if we&rsquo;re
to do any meaningful browsing on the internet?</p>
<p>Using a sound browser will go a long way in mitigating the risk of privacy
violations. Like anything else in life, it&rsquo;s important to use the right tool for
the job. If you&rsquo;re going on a road trip across the country, you&rsquo;d want to drive
the right vehicle, have spare tires, make sure the engine is healthy and won&rsquo;t
overheat, plan charging breaks if you&rsquo;re going electric, and so on.  The same
applies to browsing the internet. In this article, I&rsquo;ll go over different
security and privacy measures implemented in <a href="https://firefox.com">Firefox</a>, my favourite browser.</p>
<p>Before we dive in, let me note that my decision to run Firefox as my daily
driver is not a political one or based on Mozilla&rsquo;s management, work conditions,
or product line. I&rsquo;m only focused on the software itself and how it serves my
personal internet browsing needs.</p>
<h2 id="http-header-sanitization">HTTP header sanitization</h2>
<p>In this day and age, web pages have become remarkably complex to the point where
it&rsquo;s quite rare to find web services that don&rsquo;t pull resources from other
domains, may they be fonts, images, videos, scripts, or other types of
resources.  When a browser goes out to fetch external resources, it attaches the
original URL to every request in the <a href="https://en.wikipedia.org/wiki/HTTP_referer">HTTP Referer header</a>. The entire URL that
the user had initially put in their browser address bar ends up transmitted to
all the domains that the page being visited depends on. To give you an idea, if
a user was reading an article on a news site in dark mode with big font (URL:
<a href="https://www.allthenews.com/breaking-news-read-now?mode=dark&amp;font-size=big);">https://www.allthenews.com/breaking-news-read-now?mode=dark&amp;font-size=big);</a> and this site loaded &ldquo;share&rdquo;
buttons from Google, Twitter, and Facebook; all of these companies would be made
aware of the article the user was reading <em>and</em> the preferred settings for
article consumption.</p>
<p>Firefox <a href="https://blog.mozilla.org/security/2021/03/22/firefox-87-trims-http-referrers-by-default-to-protect-user-privacy/">trims the path and query parameters from the HTTP Referer header</a> for all
cross-origin requests by default. So in our previous example the URL would be
turned into <a href="https://www.allthenews.com/">https://www.allthenews.com/</a> when communicated as a referrer to
Google, Twitter, and Facebook, thereby reducing the amount of leaked user
information.</p>
<h2 id="cookie-store-isolation">Cookie store isolation</h2>
<p>When it comes to cookie management, Firefox has really stepped up their game. I
don&rsquo;t think any other browser handles cookies nearly as well.  Firefox not only
blocks cookies from domains that were identified as trackers, it totally
cripples cookie-based tracking with its Total Cookie Protection. It works by
operating completely isolated cookie jars, each dedicated to a website that the
user explicitly visited. By way of illustration, cookies set when the user
visits website A are only ever attached to requests triggered by user actions on
website A, including third-party cookies. This is exceptionally powerful because it
means that a third-party cookie coming from Facebook, for instance, while
browsing a news site will never make it back to Facebook when browsing any other
website that uses Facebook&rsquo;s assets.</p>
<p>In addition, Firefox&rsquo;s Enhanced Cookie Clearing (ECC) allows you to wipe out all
the data that were created by a specific website. This include first and third
party cookies, local storage data, settings, and cache. This is different than
the typical &ldquo;delete website cookies&rdquo; setting you&rsquo;ll find in other mainstream
bowsers in that ECC not only deletes data that belong to the selected website,
but also all other data that were saved in your browser while visiting that
website. Let&rsquo;s say you&rsquo;re on recipes.com that pulls in some assets
from google.com, while also being directly logged into google.com in a separate
tab. Clearing cookies of recipes.com using ECC will have the following effects:</p>
<ul>
<li>Delete all the data created by recipes.com</li>
<li>Delete all the data created by google.com <em>while browsing recipes.com</em>. This
means that we&rsquo;ll remain logged into google.com in the second tab. This is
extremely potent as it grants you the ability to make your browser
effectively forget about all your activity on a given website.</li>
</ul>
<h2 id="process-isolation">Process isolation</h2>
<p>Every time you visit a website with Javascript enabled, you run code that you&rsquo;ve
most likely never seen on your computer<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>. The vast majority of browsers do a
great job containing this code in a tight sandbox to prevent malicious
out-of-scope access, but not all enforce good segregation within the sandbox.
Firefox goes to great lengths to isolate code pulled from the internet that runs
on your machine.  With its <a href="https://hacks.mozilla.org/2021/05/introducing-firefox-new-site-isolation-security-architecture/">Site Isolation</a>, also called Project Fission, each
website is loaded in a separate Operating System (OS) process. So if you visit
&ldquo;example.com&rdquo; and, in a new tab, load &ldquo;example.org&rdquo;, these 2 websites&rsquo; code will
run in 2 OS-segregated processes, each with its own isolated memory. And this
isn&rsquo;t specific to tabs; if &ldquo;example.com&rdquo; had an iframe that loaded content from
&ldquo;example.org&rdquo;, the same thing would happen: Two processes would be spawned, one
for each domain. This drastically improves protection against timing attacks
like <a href="https://en.wikipedia.org/wiki/Spectre_(security_vulnerability)">Spectre</a> and <a href="https://en.wikipedia.org/wiki/Meltdown_(security_vulnerability)">Meltdown</a> where one process can illegitimately peak into
another&rsquo;s memory. To see Firefox processes type &ldquo;about:processes&rdquo; in the address
bar and hit &ldquo;Enter&rdquo;.</p>
<p>Moreover, Firefox is smart enough to also account for <a href="https://publicsuffix.org">Public Suffix List</a><sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>
domains where multiple sites can be served as different subdomains of the same
domain.  For instance, if you were to load &ldquo;my-site.codeberg.page&rdquo; and
&ldquo;another-site.codeberg.page&rdquo;, they would each get a separate process because
they would be considered two different websites even though they share the same
domain, since this domain is part of the Public Suffix List.</p>
<h2 id="session-isolation">Session isolation</h2>
<p>Now, let&rsquo;s go up few layers and look at what Firefox does at the session level.
While there&rsquo;s no built-in session separation beyond Private Browsing in Firefox,
their team has built the <a href="https://blog.mozilla.org/en/products/firefox/introducing-firefox-multi-account-containers/">Multi-Account Containers</a> add-on which takes
compartmentalization to the next level. Every container is a fresh browser
session with its own storage and cookie store. Think &ldquo;Private Browsing&rdquo; but not
limited to a single private session. This is particularly convenient when you
want to log into multiple accounts on the same website. Furthermore, all the
security and privacy measures we touched on so far are maintained inside each
container. When you first visit a website in a container, Firefox remembers that
and asks you the next time around if you&rsquo;d like to assign that website to that
container. Replying yes makes Firefox always open that website in the chosen
container without you having to do so every time. Who said you had to pick
between privacy and convenience again?</p>
<p>You can also set a separate Virtual Private Network (VPN) per container. For
example, if you want to use a VPN when shopping online you could create a
&ldquo;shopping&rdquo; container and configure it to always use your VPN<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup>.</p>
<h2 id="profiles">Profiles</h2>
<p>To push compartmentalization even further, Firefox allows you to manage multiple
<a href="https://support.mozilla.org/en-US/kb/profile-manager-create-remove-switch-firefox-profiles?redirectslug=profile-manager-create-and-remove-firefox-profiles&amp;redirectlocale=en-US">profiles</a> within the same installation. A profile is nothing more than a set of
user information, like bookmarks, saved passwords, settings, etc. While using
profiles is generally not needed, there are few cases where it comes in very
handy. Imagine dealing with a website that doesn&rsquo;t work properly with Firefox
strict tracking protection mode, which you should always have on by the way. You
could, short of ditching that website and finding a better alternative, spin up
a new profile where you don&rsquo;t use strict tracking protection mode for the sole
purpose of visiting that website. This can apply to anything that&rsquo;s not impacted
by the Multi-Account Containers add-on like bookmarks, extensions, and themes.</p>
<h2 id="wrap-up">Wrap up</h2>
<p>Well that was a lot of material! We&rsquo;ve covered various security and privacy
features that come with Firefox out of the box, in addition to an add-on that
brings a new dimension of compartmentalization to the mix. Things like cookie
store, session, and process isolation; HTTP header sanitization; and more.
In my view, this makes Firefox the best browser, at the time of this writing,
for a privacy-conscious internet user like myself.</p>
<p>If you know a free and open-source browser that offers all the guarantees
mentioned in this article, please bring it to my attention; I&rsquo;d love to see how
it compares to Firefox.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Provided that said website uses Javasctipt&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>Public Suffix List is a community-maintained list of effective top level
domains (eTLDs) that host different sites as subdomains, like github.io and
codeberg.page.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>This only applies to HTTP/HTTPS requests. DNS queries are still subject to
browser DNS settings and do not go through the VPN configured for the container.&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    
    <item>
      <title>DNS over HTTPS in Pihole with Docker</title>
      <link>https://ilyess.cc/posts/dns-over-https-in-pihole-with-docker/</link>
      <pubDate>Tue, 15 Feb 2022 20:00:00 -0500</pubDate>
      
      <guid>https://ilyess.cc/posts/dns-over-https-in-pihole-with-docker/</guid>
      <description>There&amp;rsquo;s a saying that goes: &amp;ldquo;Show me your friends, I&amp;rsquo;ll tell you who you are&amp;rdquo;. A slight variation of this is: &amp;ldquo;Show me the websites you visit, I&amp;rsquo;ll tell you who you are&amp;rdquo;. A lot can be learned about an individual just by examining the websites they visit, the search queries they run, and the apps they use on a regular basis. A trove of information about a user&amp;rsquo;s online activity can be gleaned from their DNS traffic.</description>
      <content:encoded><![CDATA[<p>There&rsquo;s a saying that goes: &ldquo;Show me your friends, I&rsquo;ll tell you who you are&rdquo;. A
slight variation of this is: &ldquo;Show me the websites you visit, I&rsquo;ll tell you who
you are&rdquo;. A lot can be learned about an individual just by examining the websites
they visit, the search queries they run, and the apps they use on a regular
basis. A trove of information about a user&rsquo;s online activity can be gleaned from
their DNS traffic. Have you ever wondered why practically all Internet Service
Providers (ISPs) pre-configure consumer routers with their own DNS servers?</p>
<p>In this article, we&rsquo;ll learned a bit about DoH with a quick refresher on DNS and
how it works, and go over a few strategies to improve online privacy by securing
DNS.</p>
<h2 id="whats-doh">What&rsquo;s DoH?</h2>
<p>The Domain Name System (DNS) is the system used to identify computers on the
internet. Computers can only communicate with one another if they know each
other&rsquo;s IP addresses. But IP addresses, which are basically a bunch of numbers
bundled together, are hard to memorize. If we had to use IP addresses to access
websites, the web wouldn&rsquo;t have taken off the way it did and you wouldn&rsquo;t be
reading this article online today. DNS was invented to accommodate humans, not
machines.</p>
<p>Every time you type a URL in the browser, it issues a DNS a query to your DNS
server in order to resolve the IP address of the website you&rsquo;re trying to visit.
The same thing happens when you open an app on your mobile device, when your
smart light bulb phones home to fetch the most up-to-date brightness level it
should shine at, and when you add a new show to your list on your smart TV
Netflix app. Basically, every single action you make online triggers one or more
DNS queries.</p>
<p>However, DNS was not built with privacy in mind. All DNS queries are routed
through the internet in plain text. This means that anyone sniffing traffic on a
network, or acting as a proxy to the internet, like an ISP or an enterprise
router, can see the web locations that everyone on that network is visiting.
While watching DNS traffic alone doesn&rsquo;t give out information about the
interactions between a client and a given website, it does paint a detailed
picture of the web locations that a user has visited, an estimate of how long
they have been on each website, the apps they&rsquo;re using, and the type of
Internet-of-Thing (IoT) devices they have installed in their homes, if any. It
goes without saying that this is extremely detrimental to one&rsquo;s online privacy.
Fortunately, there are solutions to this problem.</p>
<p>One way of protecting one&rsquo;s DNS queries from snooping eyes is to use encryption,
like with DNS over TLS (DoT) or DNS over HTTPS (DoT). In this article we&rsquo;ll
focus on DoH which routes DNS traffic in an encrypted HTTPS tunnel. The client
establishes a secure connection with the DNS server and funnels all DNS queries
through it. This effectively encrypts DNS communications and renders them
inaccessible to spying third parties. Now, let&rsquo;s explore a few options of
leveraging DoH to protect online activities<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> and improve privacy.</p>
<h2 id="client-settings">Client Settings</h2>
<p>Many browsers<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> nowadays offer the option to configure a custom DoH server.
However, this kind of configuration only applies to the activity happening in
the browser and as mentioned previously, not all web requests originate from a
browser. If we want to protect all DNS traffic emerging from a network, say a
user&rsquo;s home network, we should configure the network to use a local DNS server
under our control that will turn around and delegate DNS resolution to a trusted
upstream provider using DoH. First, let&rsquo;s see how we can build this local DNS
server.</p>
<h2 id="local-dns-server">Local DNS Server</h2>
<p>Cloudflare built a nice little application called <code>cloudflared</code> that converts
plain DNS queries to DoH. It&rsquo;s free, open-source and easy to run.  If you&rsquo;ve
read any other post on this blog you must&rsquo;ve realized how much I love
containers. So let&rsquo;s build a <code>cloudflared</code> Docker image to run as a local DNS
server. Unfortunately, Cloudflare doesn&rsquo;t offer an official Docker image for
<code>cloudflared</code> but we can make our own fairly easily. To do so, in a file named
<code>Dockerfile</code> put the following content:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-docker" data-lang="docker"><span style="display:flex;"><span><span style="color:#66d9ef">FROM</span><span style="color:#e6db74"> alpine:3.15</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">RUN</span> apk add --no-cache bash<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">RUN</span> wget -q https://github.com/cloudflare/cloudflared/releases/download/2022.1.2/cloudflared-linux-arm <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>	<span style="color:#f92672">&amp;&amp;</span> chmod +x cloudflared-linux-arm <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>    <span style="color:#f92672">&amp;&amp;</span> mv /cloudflared-linux-arm /usr/local/bin/cloudflared<span style="color:#960050;background-color:#1e0010">
</span></span></span></code></pre></div><p>Here, we&rsquo;re using <code>alpine</code> version 3.15 and <code>cloudflared</code> version 2022.1.2 but
more versions might have been released since this article was published. Feel
free to update these versions to the latest.</p>
<p>In order to use this Docker image we can build a <code>docker-compose</code> service to run
<code>cloudflared</code>. Alongside the previously created <code>Dockerfile</code>, let&rsquo;s make a new
file called <code>docker-compose.yml</code> with this content:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-docker" data-lang="docker"><span style="display:flex;"><span>version: <span style="color:#e6db74">&#34;3.8&#34;</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>services:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  cloudflared:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    build: .<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    ports:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>      - <span style="color:#e6db74">&#34;53:5053&#34;</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    <span style="color:#75715e"># replare &lt;UPSTREAM SERVER&gt; with the URL for the upstream DoH server</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    <span style="color:#75715e"># e.g.: https://doh.libredns.gr/dns-query</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    entrypoint: bash -c <span style="color:#e6db74">&#34;cloudflared proxy-dns --port 5053 --address 0.0.0.0 --upstream &lt;UPSTREAM SERVER&gt;&#34;</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    restart: unless-stopped<span style="color:#960050;background-color:#1e0010">
</span></span></span></code></pre></div><p>Make sure to replace <code>&lt;UPSTREAM SERVER&gt;</code> in the <code>entrypoint</code> command with your
DoH server of choice. PrivacyGuides lists <a href="https://privacyguides.org/providers/dns/">a few options</a>
you can pick from if you don&rsquo;t already have a favourite DNS server. At this
point, you have everything you need to convert all DNS queries in your network
into DoH. Here are the steps to accomplish this:</p>
<ol>
<li>Run <code>docker-compose up cloudflared</code> from the folder where you put the
<code>Dockerfile</code> and <code>docker-compose.yml</code> files to spin up a <code>cloudflared</code>
container. Ideally, this should be done on a machine that&rsquo;s constantly
running on your network with a static IP address. A Raspberry Pi or a home
server are great candidates for this.</li>
<li>Configure your network&rsquo;s DHCP server to use the machine where <code>cloudflared</code>
is running as its default DNS server. This will automatically instruct all
devices on the network to use the <code>cloudflared</code> container for DNS resolution.
If you don&rsquo;t have control over your DHCP server, or it&rsquo;s too complicated to
reconfigure at the moment, you can always start off by manually editing the
DNS configuration on your most-used devices.</li>
</ol>
<h2 id="doh-in-pihole">DoH in Pihole</h2>
<p>If you already have a Pihole docker container running in your network and
serving DNS queries, you can now set the <code>cloudflared</code> container as an upstream
DNS server in Pihole and automatically upgrade all DNS queries to DoH. We&rsquo;ve
seen in <a href="/posts/pihole-dhcp-docker-bridge-network">a previous article</a> how to set up Pihole using Docker. We&rsquo;ll use the same
<code>docker-compose</code> file here to illustrate how to integrate <code>cloudflared</code>.</p>
<p>First we need to place the <code>Dockerfile</code> file we created in the previous section
inside a folder called <code>cloudflared</code>. Then, using our previous Pihole
<code>docker-compose</code> file, we can add a new service for <code>cloudflared</code> as shown
below:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-docker" data-lang="docker"><span style="display:flex;"><span>version: <span style="color:#e6db74">&#34;3.8&#34;</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>services:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  pihole:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    <span style="color:#75715e"># ...</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    environment:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>      <span style="color:#75715e"># ...</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>      - PIHOLE_DNS_<span style="color:#f92672">=</span>172.31.0.200#5350<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    <span style="color:#75715e"># ...</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    depends_on:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>      - cloudflared<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>      - dhcphelper<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    <span style="color:#75715e"># ...</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  dhcphelper:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    <span style="color:#75715e"># ...</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  cloudflared:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    build: ./cloudflared<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    environment:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>      <span style="color:#75715e"># set UPSTREAM_PROVIDER to the URL for the upstream DOH server</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>      <span style="color:#75715e"># e.g.: https://doh.libredns.gr/dns-query</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>      - UPSTREAM_PROVIDER<span style="color:#f92672">=</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    entrypoint: bash -c <span style="color:#e6db74">&#34;cloudflared proxy-dns --port 5053 --address 0.0.0.0 --upstream </span>$$<span style="color:#e6db74">UPSTREAM_PROVIDER&#34;</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    networks:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>      backend:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>        ipv4_address: <span style="color:#e6db74">&#39;172.31.0.200&#39;</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    restart: unless-stopped<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>networks:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  <span style="color:#75715e"># ...</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>volumes:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  <span style="color:#75715e"># ...</span><span style="color:#960050;background-color:#1e0010">
</span></span></span></code></pre></div><p>You&rsquo;ll notice that the <code>cloudflared</code> service looks similar to the one we built
in the previous section. Here, we don&rsquo;t need to expose the port 53 to the host
because the only client connecting to the <code>cloudflared</code> container is the Pihole
container running in the same Docker network. Also, we have to set a static IP
address for the <code>cloudflared</code> container and add it to the <code>PIHOLE_DNS_</code>
environment variable of Pihole in order for it to be used as an upstream DNS
server.</p>
<p>With the modifications above, restart your Pihole container by running
<code>docker-compose down</code> and bringing it back up with <code>docker-compose up pihole</code>,
and you should have a <code>cloudflared</code> container running alongside Pihole, ready to
receive requests.</p>
<h2 id="wrap-up">Wrap up</h2>
<p>Congratulations, you made it! You now understand why protecting your DNS traffic
is paramount to improving your online privacy and have in your toolbox 3
strategies for doing so, some more potent than others. In case you can&rsquo;t run a
full-fledged Pihole and <code>cloudflared</code> setup on your network, or don&rsquo;t have the
time to set that up yet, at least configure your most-frequently-used browser to
resolve DNS through DoH. It takes almost no time, and goes a long way in getting
you to a better place when it comes to your online privacy.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Keep in mind that this only protects DNS queries. It is still possible for
an adversary to figure out the websites you visit by doing reverse DNS
resolution on the IP addresses of those websites.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>Here&rsquo;s an <a href="https://support.mozilla.org/en-US/kb/firefox-dns-over-https">example</a> for Firefox.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    
    <item>
      <title>My Journey with Password Managers</title>
      <link>https://ilyess.cc/posts/my-journey-with-password-managers/</link>
      <pubDate>Tue, 28 Dec 2021 20:00:00 -0500</pubDate>
      
      <guid>https://ilyess.cc/posts/my-journey-with-password-managers/</guid>
      <description>Password managers have become a crucial tool that every online user must rely on. Since they handle extremely sensitive data, like login credentials, credit card information, secret notes, and more, careful consideration is recommended when choosing a password manager. This article goes over the phases I&amp;rsquo;ve gone through in my experience with password managers, the different solutions I used, and how I got where I am today.
First Password Manager My journey with password managers started way back when I used the &amp;ldquo;save password&amp;rdquo; feature in the Chrome browser.</description>
      <content:encoded><![CDATA[<p><img loading="lazy" src="/images/padlock.webp" alt="padlock"  />
</p>
<p>Password managers have become a crucial tool that every online user must rely
on. Since they handle extremely sensitive data, like login credentials, credit
card information, secret notes, and more, careful consideration is recommended
when choosing a password manager. This article goes over the phases I&rsquo;ve gone
through in my experience with password managers, the different solutions I used,
and how I got where I am today.</p>
<h2 id="first-password-manager">First Password Manager</h2>
<p>My journey with password managers started way back when I used the &ldquo;save
password&rdquo; feature in the Chrome browser. I was still &ldquo;generating&rdquo; passwords
myself but relying on Chrome to save and automatically insert them when I&rsquo;m on
the login page of various websites. At that time, I didn&rsquo;t really have too many
accounts - probably no more than a dozen. But as the web started turning into a
plethora of sign-up walls, where you can&rsquo;t hover on a button without having to
log in first, I quickly ran out of ideas to create new unique
passwords. This is the point where I knew I needed a proper password manager.</p>
<p>Around this time, <a href="https://www.lastpass.com">Lastpass</a> was one of the most dominant actors in the field of
password management and they were doing some good work. So I decided to give
Lastpass a try and I really liked it. The convenience of not having to come up
with a &ldquo;secure&rdquo; password that I haven&rsquo;t used before when signing up on a new
website, brought such a relief to my account creation flow and reduced a
considerable amount of friction. Lastpass had some issues however.
<a href="https://www.wired.com/2015/06/hack-brief-password-manager-lastpass-got-breached-hard/">The data breach of 2015</a>
was the first thing to sound the alarm for me. I started to realize
that fully trusting an entity with my most valuable data, the keys to every
single account I own, was probably not a good idea. To make matters more scary,
Lastpass does not publish their source code for others to view, analyze, and
even contribute to. Security by obscurity is never the way to go.
Furthermore, <a href="https://www.theverge.com/2021/2/26/22302709/lastpass-android-app-trackers-security-research-privacy">they include third-party trackers</a>
in their codebase which is a very bad practice for a security-critical service
like Lastpass.</p>
<p>I&rsquo;m not trying to sabotage Lastpass here, or imply that their security is
lacking. As a matter of fact, their security model seems to be solid. As far
as I know, there has never been any breach that exposed user sensitive data like
passwords in clear text. All I&rsquo;m saying is that it wasn&rsquo;t the right fit for me.
Not to mention that their recent <a href="https://blog.lastpass.com/2021/02/changes-to-lastpass-free/">up-sell push</a>
to convert free users to premium by restructuring their pricing and feature
models just didn&rsquo;t sit right with me.  They were turning into another Big Tech
player, and it was time for me to walk away.</p>
<h2 id="gaining-back-control-over-my-data">Gaining back control over my data</h2>
<p>When I started using Lastpass, there weren&rsquo;t many options to choose from. But
few years later, other password managers started popping up and one of them was
particularly good at distinguishing itself amongst the privacy-focused
community. This password manager is no other than <a href="https://keepassxc.org/">KeepassXC</a>. I believe it&rsquo;s the
first open-source password manager that I was made aware of and I instantly fell
for it.  It&rsquo;s a free, community-driven, offline password manager that I simply
couldn&rsquo;t ignore. Next thing you know, I was migrating all of my passwords off of
Lastpass and celebrating the addition of yet another open-source tool to my
arsenal.</p>
<p>The only problem that I had to solve was synchronizing data across multiple
devices. With a cloud-based service like Lastpass, this issue is taken care of
by the service itself and changes on one device are replicated across the others
automatically. With an offline password manager like KeepassXC on the other
hand, data synchronization becomes the responsibility of the user. So, I moved
all my credentials to a KeepassXC database (DB), and used a cloud service to
regularly back it up.  When I needed a fresh version of my DB in any of my
devices, I either transferred it directly within my home network, or pulled it
from the cloud service provider, and used an open-source client to read it. To
make things simple, I also only ever made changes to the DB on my computer, and
stuck to read-only mode on the other devices. You can imagine how this could
sometimes prove tedious, especially when I needed to update existing or create
new entries in my credentials list from a device other than my main computer. I
started to seriously miss the convenience of the seamless synchronization I
enjoyed with Lastpass, but I wasn&rsquo;t ready to downgrade my security and privacy
just for that.</p>
<h2 id="the-best-of-all-worlds">The best of all worlds</h2>
<p>Luckily, there was this new kid on the block, a new solution that ticked all the
boxes for me - it&rsquo;s open source, free<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>, and self-hostable! This meant that I
could get all the benefits of data control I have with KeepassXC <em>and</em> the
convenience of seamless multi-device synchronization. The solution I&rsquo;m referring
to is <a href="https://bitwarden.com/">Bitwarden</a>. Ever since I launched <a href="/posts/self-host-bitwarden-using-docker">my own instance of Bitwarden</a>,
I&rsquo;ve never looked back. I truly love this piece of software and am so grateful
to the amazing people behind it. Plus, the beauty of it all is that when you
host your own instance you get all of the premium features for free!</p>
<h2 id="wrap-up">Wrap up</h2>
<p>As you see, I wasn&rsquo;t fortunate enough to pick a winner right from the start. I
began my journey naive, prioritizing convenience without paying much attention
to security, let alone privacy. As I learned more about password managers, I
shifted my priorities and started giving more weight to security and privacy,
even at the expense of convenience at times.</p>
<p>If you have comments or suggestions, or if you just want to strike a
conversation on this topic, feel free to hit me up on <a href="https://mastodon.online/@ilyess">Mastodon</a>.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>They also offer premium plans but their free tier has all the basic
features expected in a password manager.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    
    <item>
      <title>Self-Host Bitwarden using Docker</title>
      <link>https://ilyess.cc/posts/self-host-bitwarden-using-docker/</link>
      <pubDate>Wed, 02 Jun 2021 20:00:00 -0400</pubDate>
      
      <guid>https://ilyess.cc/posts/self-host-bitwarden-using-docker/</guid>
      <description>Bitwarden is a password manager that allows users to generate and store strong passwords. It also handles other types of data like secure notes and credit card information. At the time of this writing, it is one of the best password managers out there because in addition to offering strong and zero-knowledge encryption, the codebase is open source. This means that anyone can inspect the code and run it for their personal use.</description>
      <content:encoded><![CDATA[<p><img loading="lazy" src="/images/bitwarden.webp" alt="bitwarden"  />
</p>
<p><a href="https://bitwarden.com">Bitwarden</a> is a password manager that allows users to generate and store strong
passwords. It also handles other types of data like secure notes and credit card
information. At the time of this writing, it is one of the best password
managers out there because in addition to offering strong and zero-knowledge
encryption, the codebase is <a href="https://bitwarden.com/open-source/">open source</a>. This means that anyone can inspect the
code and run it for their personal use. In this article, we&rsquo;ll go over the steps
to build a fully functioning Bitwarden instance that anyone can run on a server
at home.</p>
<h2 id="docker-setup">Docker setup</h2>
<p>Let&rsquo;s start by setting up a <code>docker-compose.yml</code> file to run the Bitwarden server:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-docker" data-lang="docker"><span style="display:flex;"><span>version: <span style="color:#e6db74">&#34;3.8&#34;</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>services:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  bitwarden:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    container_name: <span style="color:#e6db74">&#34;bitwarden&#34;</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    image: vaultwarden/server<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    ports:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>      - <span style="color:#e6db74">&#34;3012:3012&#34;</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    volumes:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>      - data:/data<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>volumes:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  data:<span style="color:#960050;background-color:#1e0010">
</span></span></span></code></pre></div><p>Here, we&rsquo;re using vaultwarden/server image which is a rust-based version of
Bitwarden server. We&rsquo;re exposing a TCP port to be able to communicate with the
container from outside of Docker network. Then, we create a volume and mount it
on /data inside the container. That&rsquo;s where Bitwarden stores its data, including
users, vaults, and attachments.</p>
<p>If we bring up this container and try to access Bitwarden through
<code>https://localhost:3012</code>, it will not work.  Bitwarden requires all communications
to go through a TLS tunnel. In other words, we need to use HTTPS instead of
HTTP. We&rsquo;re going to use a second container running Nginx to handle TLS for us
and proxy requests to Bitwarden&rsquo;s container.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-docker" data-lang="docker"><span style="display:flex;"><span>version: <span style="color:#e6db74">&#34;3.8&#34;</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>services:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  bitwarden:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    <span style="color:#75715e"># ...</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    depends_on:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>      - nginx<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  nginx:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    image: nginx:1.21.0-alpine<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    ports:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>      - <span style="color:#e6db74">&#34;23984:443&#34;</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    volumes:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>      - ./nginx/ssl:/etc/ssl:ro<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>      - ./nginx/conf.d:/etc/nginx/conf.d:ro<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    restart: unless-stopped<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>volumes:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  data:<span style="color:#960050;background-color:#1e0010">
</span></span></span></code></pre></div><p>In this configuration, we&rsquo;re declaring a new service <code>nginx</code> using the alpine
image of <code>nginx</code>, exposing a port so that the container can be reacher from
outside Docker&rsquo;s internal network, and specifying a couple of read-only volumes.
We&rsquo;re also instructing Docker to automatically restart this container unless it
was manually stopped. This can help increase availability by automatically
recovering from sudden crashes.</p>
<p>The final result looks like follows. Notice that we introduced a environment
variable <code>ADMIN_TOKEN</code> that can be used to access the admin section of
Bitwarden. More on how to do this later.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-docker" data-lang="docker"><span style="display:flex;"><span>version: <span style="color:#e6db74">&#34;3.8&#34;</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>services:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  bitwarden:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    container_name: <span style="color:#e6db74">&#34;bitwarden&#34;</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    image: vaultwarden/server<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    environment:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>      - ADMIN_TOKEN<span style="color:#f92672">=</span>SOME_SECRET_TOKEN<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    volumes:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>      - data:/data<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    depends_on:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>      - nginx<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  nginx:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    image: nginx:1.21.0-alpine<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    ports:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>      - <span style="color:#e6db74">&#34;23984:443&#34;</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    volumes:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>      - ./nginx/ssl:/etc/ssl:ro<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>      - ./nginx/conf.d:/etc/nginx/conf.d:ro<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    restart: unless-stopped<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>volumes:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  data:<span style="color:#960050;background-color:#1e0010">
</span></span></span></code></pre></div><h2 id="tls-support">TLS Support</h2>
<p>Now that we have our containers set up, it&rsquo;s time to configure Nginx to handle
TLS connections and forward them to Bitwarden&rsquo;s container. Let&rsquo;s create a
server configuration file named <code>bitwarden.conf</code> and place it under
<code>nginx/conf.d/</code>. This path relative to wherever you place the
<code>docker-compose.yml</code> file.</p>
<pre tabindex="0"><code>server {
    listen 443 ssl http2;

    # Allow large attachments
    client_max_body_size 128M;

    location / {
        proxy_pass http://bitwarden:80;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    location /notifications/hub {
        proxy_pass http://bitwarden:3012;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection &#34;upgrade&#34;;
    }

    location /notifications/hub/negotiate {
        proxy_pass http://bitwarden:80;
    }
}
</code></pre><p>This is a pretty typical Nginx proxy configuration where we define a port to
listen to along with protocols we want to handle. Next, we have a directive to
allow large attachments<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> and we declare few paths and how they should be
handled. We&rsquo;re not going to dive into each directive used here but If you want
to know more about how to configure Nginx, I encourage to check out their
<a href="https://nginx.org/en/docs/">official documentation.</a></p>
<p>If you&rsquo;ve been following along you&rsquo;ll notice that we haven&rsquo;t addressed the SSL
connection yet, and without it our setup will not work. So let&rsquo;s figure that
out. In case you&rsquo;re planning to use a real domain name for your Bitwarden instance,
you can skip this section and jump directly to <a href="https://ilyess.cc/posts/self-host-bitwarden-using-docker/#nginx-configuration">Nginx Configuration</a>.</p>
<p>In order to make a self-signed TLS certificate, first we need to generate a
private key and a certificate for our Bitwarden server using the following
command.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>OUT_FOLDER<span style="color:#f92672">=</span>/path/to/ssl
</span></span><span style="display:flex;"><span>DOMAIN<span style="color:#f92672">=</span>your.bitwarden.domain
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>openssl req -x509 -nodes -days <span style="color:#ae81ff">365</span> -newkey rsa:4096 <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>        -config &lt;<span style="color:#f92672">(</span>cat /etc/ssl/openssl.cnf &lt;<span style="color:#f92672">(</span>printf <span style="color:#e6db74">&#34;[SAN]\nsubjectAltName=DNS:</span>$DOMAIN<span style="color:#e6db74">\nbasicConstraints=CA:true&#34;</span><span style="color:#f92672">))</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>        -keyout $OUT_FOLDER/private/nginx-bitwarden.key <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>        -out $OUT_FOLDER/certs/nginx-bitwarden.cert <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>        -reqexts SAN -extensions SAN <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>        -subj <span style="color:#e6db74">&#34;/C=US/ST=New York/L=New York/O=Company Name/OU=Bitwarden/CN=</span>$DOMAIN<span style="color:#e6db74">&#34;</span>
</span></span></code></pre></div><p>This will generate a new public key <code>nginx-bitwarden.key</code> using RSA with a key
length of <code>4096</code> bits, and a self-signed TLS certificate <code>nginx-bitwarden.cert</code>
valid for <code>365</code> days in the <code>private/</code> and <code>certs/</code> folders respectively. These
folders should be placed under the folder <code>ssl/</code> that lives alongside our
<code>docker-compose.yml</code> file, so make sure you set the <code>$OUT_FOLDER</code> variable
properly. In addition, the <code>$DOMAIN</code> should be set to the fully qualified domain
name of the machine where you&rsquo;re planning to run Bitwarden and Nginx. It&rsquo;s not
complicated to assign an FQDN to your local machine using a local DNS,
especially if you&rsquo;re running Pihole. If you&rsquo;re interested in getting Pihole set
up locally with Docker, check out this <a href="https://ilyess.cc/posts/pihole-dhcp-docker-bridge-network/">article</a>.</p>
<h3 id="nginx-configuration">Nginx Configuration</h3>
<p>Now that we have our certificate and private key, we should tell Nginx where
they are located in order to use them for TLS connections.</p>
<pre tabindex="0"><code>server {
    listen 443 ssl http2;

    ssl_certificate      /etc/ssl/certs/nginx-bitwarden.crt;
    ssl_certificate_key  /etc/ssl/private/nginx-bitwarden.key;

    # ...
}
</code></pre><p>It&rsquo;s recommended to generate a Diffie-Hellman file for stronger connections and
use it in our Nginx configuration. We can generate one with the command:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>openssl dhparam -out $OUT_FOLDER/certs/dhparam.pem <span style="color:#ae81ff">4096</span>
</span></span></code></pre></div><p>To be honest, I haven&rsquo;t yet looked at the functions of this Diffie-Hellman file
to fully understand how it improves security. But if you know more about it
and are interested in contributing your knowledge to this article, feel free to
reach out to me on <a href="https://mastodon.online/@ilyess">Mastodon</a>.</p>
<p>The complete Nginx configuration after linking the <code>.pem</code> file should look like
follows.</p>
<pre tabindex="0"><code>server {
    listen 443 ssl http2;

    ssl_certificate      /etc/ssl/certs/nginx-bitwarden.crt;
    ssl_certificate_key  /etc/ssl/private/nginx-bitwarden.key;

    ssl_dhparam /etc/ssl/certs/dhparam.pem;

    client_max_body_size 128M;

    location / {
        proxy_pass http://bitwarden:80;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    location /notifications/hub {
        proxy_pass http://bitwarden:3012;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection &#34;upgrade&#34;;
    }

    location /notifications/hub/negotiate {
        proxy_pass http://bitwarden:80;
    }
}
</code></pre><p>Finally, we have our containers configured to run Bitwarden. All we need do to
in order to bring them up is run:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>docker-compose up -d bitwarden
</span></span></code></pre></div><p>Once the containers are up, you&rsquo;ll be able to reach Bitwarden through its domain
name. Given our example configuration, that would be
<code>https://your.bitwarden.domain</code>. The first time you open the page, your browser
will most likely complain about an insecure connection and display a flashy
warning.  If it doesn&rsquo;t, stop using this browser and get yourself a real one!
This is due to the self-signed certificate. Since the certificate is not signed
by a Certificate Authority (CA) present in the browsers or the OS&rsquo;s root CA
store, the browser considers the connection insecure. You can add an exception
for this certificate in your browser so that it doesn&rsquo;t freak out every time you
access your self-hosted Bitwarden. Similarly, you will have to install this
certificate on your device and trust it, otherwise the OS will prevent
Bitwarden&rsquo;s app to communicate with our running container. To do so, send the
<code>nginx-bitwarden.crt</code> file we generated in the <a href="https://ilyess.cc/posts/self-host-bitwarden-using-docker/#tls-support">TLS Support section</a> to your
device<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>, open it and follow the installation steps. It should be
straightforward in most OS&rsquo;s but it&rsquo;s always safe to follow the official guide
to make sure you cover all the steps. For instance, in iOS, on top of installing
the certificate you have to explicitly trust it for TLS connections.</p>
<h2 id="admin-access">Admin Access</h2>
<p>Like we&rsquo;ve seen in a previous section, in order to unlock the admin panel we
need to set a token in the <code>ADMIN_TOKEN</code> environment variable. Let&rsquo;s generate a
token using the following command.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>openssl rand -base64 <span style="color:#ae81ff">48</span>
</span></span></code></pre></div><p>Once Bitwarden&rsquo;s container is restarted after setting the admin token, you can
access the admin panel at <code>https://your.bitwarden.domain/admin</code>.</p>
<h2 id="wrap-up">Wrap up</h2>
<p>We finally made it. We built a docker setup running a container for the
Bitwarden server with an Nginx proxy handling TLS connections using a
self-signed certificate. This grants us full control over our credentials and
any other data we choose to store in Bitwarden, all without having to trust any
third party to handle this for us.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>This one is optional and is only necessary if you use Bitwarden for
attachments or to send large files.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>It&rsquo;s safe to use email here since the certificate doesn&rsquo;t contain any
sensitive information.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    
    <item>
      <title>Pihole DHCP and Docker Bridge Network</title>
      <link>https://ilyess.cc/posts/pihole-dhcp-docker-bridge-network/</link>
      <pubDate>Tue, 25 May 2021 13:00:00 -0400</pubDate>
      
      <guid>https://ilyess.cc/posts/pihole-dhcp-docker-bridge-network/</guid>
      <description>Pihole is a great tool to protect your home network from trackers and annoying ads. It can be deployed either directly on a server or in a Docker container. I personally lean toward using Docker whenever possible for the flexibility and isolation it provides. Services deployed in Docker containers are significantly easier to migrate than raw installations and Pihole is no exception.
Running Pihole on Docker is pretty straightforward, but things start to get a bit complicated when it comes to enabling DHCP - using Pihole to serve DHCP requests.</description>
      <content:encoded><![CDATA[<p><a href="https://pi-hole.net/">Pihole</a> is a great tool to protect your home network from
trackers and annoying ads. It can be deployed either directly on a server or in
a Docker container. I personally lean toward using Docker whenever possible for
the flexibility and isolation it provides. Services deployed in Docker
containers are significantly easier to migrate than raw installations and Pihole
is no exception.</p>
<p>Running Pihole on Docker is pretty straightforward, but things start to get a
bit complicated when it comes to enabling DHCP - using Pihole to serve DHCP
requests. With Docker&rsquo;s default bridge network mode, we can&rsquo;t use Pihole as a
DHCP server. So we have two options: (1) use the host network
mode, or (2) run a DHCP relay (more on this later).</p>
<p>Option (1) is the easiest because all we&rsquo;d have to do is set <code>network_mode: &quot;host&quot;</code> in the service definition, but it comes with a considerable
disadvantage. It undermines the network isolation provided by Docker&rsquo;s bridge
driver, not to mention that it will take up ports from the host&rsquo;s network which
could be a concern depending on what&rsquo;s running on your server. Option
(2) allows us to stick to the bridge mode but adds a good amount of complexity.
If you&rsquo;re anything like me, you&rsquo;ll favour security and try to maintain Docker&rsquo;s
network isolation.</p>
<p>Option (2) it is. Make sure your cup of coffee is full, and let&rsquo;s dive in!</p>
<h2 id="pihole-on-docker">Pihole on Docker</h2>
<p>First, let&rsquo;s build a <code>docker-compose.yml</code> file with a basic configuration to run
Pihole.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-docker" data-lang="docker"><span style="display:flex;"><span>version: <span style="color:#e6db74">&#34;3.2&#34;</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>services:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  pihole:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    container_name: pihole<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    image: pihole/pihole:latest<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    ports:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>      - <span style="color:#e6db74">&#34;53:53/tcp&#34;</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>      - <span style="color:#e6db74">&#34;53:53/udp&#34;</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>      - <span style="color:#e6db74">&#34;8080:80/tcp&#34;</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    environment:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>      - TZ<span style="color:#f92672">=</span>America/New_York<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>      - WEBPASSWORD<span style="color:#f92672">=</span>very-secure-password <span style="color:#75715e"># Choose a password for admin</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>      - PIHOLE_DNS_<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;1.1.1.1;1.0.0.1&#34;</span> <span style="color:#75715e"># Set upstream DNS servers</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>      - ServerIP<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;10.10.0.10&#34;</span> <span style="color:#75715e"># Your host&#39;s external IP</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    volumes:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>      - pihole:/etc/pihole/<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>      - dnsmasqd:/etc/dnsmasq.d/<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    restart: unless-stopped<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>volumes:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  pihole:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  dnsmasqd:<span style="color:#960050;background-color:#1e0010">
</span></span></span></code></pre></div><p>In this example, we define a service called <code>pihole</code> using the official Pihole
Docker image. Then, we publish few ports to get DNS requests (UDP port 53)
forwarded to the container running Pihole, and to allow access to the web panel
(TCP port 80). After that, we set some environment variables:</p>
<ul>
<li><code>WEBPASSWORD</code><sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>: The password to use when logging into the admin panel.</li>
<li><code>PIHOLE_DNS_</code>: A semicolon separated list of upstream DNS servers. These are
the servers Pihole will reach out to in order to resolve DNS queries.</li>
<li><code>ServerIP</code>: The external host IP. This is the IP that machines on the network
will send their DNS queries to. In other words, this should be the hosts IP on
the LAN network.</li>
</ul>
<p>Last, we define a couple of volumes to persist configuration across multiple
container restarts.</p>
<h2 id="dhcp-through-dockers-bridge-network">DHCP through Docker&rsquo;s Bridge Network</h2>
<p>At this point, we have a working Pihole deployment ready to manage DNS on the
host&rsquo;s network.  Since we didn&rsquo;t specify the network mode in the docker-compose
file, the container is provisioned using the default driver: bridge network.
This means that Pihole lives in a separate network than the host along with
other machines on the LAN. Docker daemon bridges these 2 networks and forwards
ports as specified in the docker-compose file. This works great for DNS, but not
so much for DHCP. If we tried to enable DHCP on Pihole&rsquo;s web interface, it would
unfortunately not work. To understand why, let&rsquo;s go over a brief overview of how
DHCP works.</p>
<p>When a client joins a network it starts broadcasting a DHCP discovery request on
that same network on UDP port 67. If there&rsquo;s a DHCP server listening, it will
respond to this request with a lease offer that the client then accepts<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>.
This is how machines get assigned IP addresses dynamically when joining a
network.</p>
<p>DHCP is not routable however, so discovery requests don&rsquo;t span across different
networks. In our setup, since Pihole is running in a Docker container with
bridge network mode, it lives in a separate network (Docker internal network)
than the LAN, so DHCP discovery requests stop at the edge of the LAN and never
make it to Pihole. To solve this, we need a DHCP relay connected to both the LAN
and Docker&rsquo;s internal network. It will intercept discovery requests on one
network and forward them to Pihole on the other.</p>
<p>With the theory out of the way, let&rsquo;s move on to a practical solution to our
problem. We will use <code>dhcp-helper</code> as a DHCP relay. First, we create an small
image with that package and place the <code>Dockerfile</code> in a folder called
<code>dhcp-helper</code>.</p>
<pre tabindex="0"><code>FROM alpine:latest
RUN apk --no-cache add dhcp-helper
EXPOSE 67 67/udp
ENTRYPOINT [&#34;dhcp-helper&#34;, &#34;-n&#34;]
</code></pre><p>The file content is self explanatory. We&rsquo;re using an <code>alpine</code> image, adding the
<code>dhcp-helper</code> package, exposing the UDP port 67 in order to receive DHCP
discovery requests, and we&rsquo;re running the helper command at startup.</p>
<p>The next step is to update the original Pihole docker-compose file to include
the DHCP relay. We will need to add a service for the relay, an additional
network with a fixed IP for the Pihole container, and make the Pihole service
depend on the DHCP relay.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-docker" data-lang="docker"><span style="display:flex;"><span>version: <span style="color:#e6db74">&#34;3.2&#34;</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>services:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  pihole:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    container_name: pihole<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    <span style="color:#75715e"># ...</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  dhcphelper:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    build: ./dhcp-helper<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    restart: unless-stopped<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    network_mode: <span style="color:#e6db74">&#34;host&#34;</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    command: -s 172.31.0.111<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    cap_add:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>      - NET_ADMIN<span style="color:#960050;background-color:#1e0010">
</span></span></span></code></pre></div><p>Notice that we&rsquo;re using the <code>host</code> network mode for the DHCP relay service. This
is important if we want to intercept discovery requests on the same network as
the host (LAN). Also, we&rsquo;re adding a flag to the entry point command specifying
the server&rsquo;s IP. We&rsquo;ll assign this IP to Pihole&rsquo;s service as follows:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-docker" data-lang="docker"><span style="display:flex;"><span>version: <span style="color:#e6db74">&#34;3.2&#34;</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>services:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  pihole:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    container_name: pihole<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    <span style="color:#75715e"># ...</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    networks:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>      backend:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>        ipv4_address: <span style="color:#e6db74">&#39;172.31.0.111&#39;</span><span style="color:#960050;background-color:#1e0010">
</span></span></span></code></pre></div><p>In order to use this new network that we named <code>backend</code> we need to define it in
our docker-compose file:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-docker" data-lang="docker"><span style="display:flex;"><span>version: <span style="color:#e6db74">&#34;3.2&#34;</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>services:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  pihole:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    container_name: pihole<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    <span style="color:#75715e"># ...</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  dhcphelper:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    build: ./dhcp-helper<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    <span style="color:#75715e"># ...</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>networks:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  backend:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    ipam:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>      config:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>        - subnet: 172.31.0.0/16<span style="color:#960050;background-color:#1e0010">
</span></span></span></code></pre></div><p>With all these modifications, we end up with the following docker-compose file:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-docker" data-lang="docker"><span style="display:flex;"><span>version: <span style="color:#e6db74">&#34;3.2&#34;</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>services:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  pihole:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    container_name: pihole<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    image: pihole/pihole:latest<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    ports:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>      - <span style="color:#e6db74">&#34;53:53/tcp&#34;</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>      - <span style="color:#e6db74">&#34;53:53/udp&#34;</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>      - <span style="color:#e6db74">&#34;8080:80/tcp&#34;</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    environment:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>      - TZ<span style="color:#f92672">=</span>America/New_York<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>      - WEBPASSWORD<span style="color:#f92672">=</span>very-secure-password <span style="color:#75715e"># Choose a password for admin</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>      - PIHOLE_DNS_<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;1.1.1.1;1.0.0.1&#34;</span> <span style="color:#75715e"># Set upstream DNS servers</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>      - ServerIP<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;10.10.0.10&#34;</span> <span style="color:#75715e"># Your host&#39;s external IP</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    volumes:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>      - pihole:/etc/pihole/<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>      - dnsmasqd:/etc/dnsmasq.d/<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    restart: unless-stopped<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    depends_on:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>      - dhcphelper<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    cap_add:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>      - NET_ADMIN<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    networks:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>      backend:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>        ipv4_address: <span style="color:#e6db74">&#39;172.31.0.111&#39;</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  dhcphelper:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    build: ./dhcp-helper<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    restart: unless-stopped<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    network_mode: <span style="color:#e6db74">&#34;host&#34;</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    command: -s 172.31.0.111<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    cap_add:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>      - NET_ADMIN<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>networks:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  backend:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    ipam:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>      config:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>        - subnet: 172.31.0.0/16<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>volumes:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  pihole:<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  dnsmasqd:<span style="color:#960050;background-color:#1e0010">
</span></span></span></code></pre></div><h2 id="one-more-thing">One More Thing</h2>
<p>So far, we&rsquo;ve managed to:</p>
<ol>
<li>Run a Pihole container with bridge network mode</li>
<li>Run a DHCP relay container that listens for discovery requests on the LAN and
forwards them to Pihole on Docker&rsquo;s internal network.</li>
</ol>
<p>However, by default Pihole sends out leases instructing clients to use its IP as
a DNS server. The IP used here is Pihole&rsquo;s IP address on the network where it
received the DHCP request. This means that the clients will be configured to
send their DNS queries to an address on the network <code>172.31.0.0/16</code> and this
will not work. This is an internal Docker network that we created to connect
Pihole to the DHCP realy, and that machines on the LAN have no access to. In
order to resolve this, Pihole needs to grant DHCP leases with the host&rsquo;s
<strong>external</strong> IP as the DNS. we can accomplish this by adding a configuration option
to <code>dnsmasq</code> running inside Pihole&rsquo;s container. This is as simple as creating a
file inside Pihole&rsquo;s container under <code>/etc/dnsmasq.d/</code> with the following:</p>
<pre tabindex="0"><code>dhcp-option=option:dns-server,&lt;PIHOLE_HOST_EXTERNAL_IP&gt;
</code></pre><p>To make our lives easier, let&rsquo;s put this in a script file called
<code>update-dhcp-dns</code> and also support multiple DNS servers while we&rsquo;re at it.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span><span style="color:#75715e">#!/bin/bash
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span>DNS_SERV_IPS<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;&#34;</span> <span style="color:#75715e"># A comma separated list of DNS IPs. E.g.: &#34;10.10.0.10,1.1.1.1,1.0.0.1&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#f92672">[[</span> -z $DNS_SERV_IPS <span style="color:#f92672">]]</span>; <span style="color:#66d9ef">then</span>
</span></span><span style="display:flex;"><span>    echo <span style="color:#e6db74">&#34;Please set DNS servers IPs by modifying the script file.&#34;</span>
</span></span><span style="display:flex;"><span>    exit 1;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">fi</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>docker-compose exec pihole bash -c <span style="color:#e6db74">&#34;echo &#39;dhcp-option=option:dns-server,</span>$DNS_SERV_IPS<span style="color:#e6db74">&#39; &gt; /etc/dnsmasq.d/07-dhcp-options&#34;</span>
</span></span></code></pre></div><h2 id="wrap-up">Wrap up</h2>
<p>We finally have all the pieces we need to run Pihole in a Docker container with
bridge network mode and have it serve DHCP requests on the host&rsquo;s LAN. After
updating <code>docker-compose.yml</code> and <code>update-dhcp-dns</code> with your desired
configuration values, like the web password, server IP, and DNS IPs, you can
deploy using the following commands.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>docker-compose up -d pihole
</span></span><span style="display:flex;"><span>./update-dhcp-dns
</span></span></code></pre></div><p>Note that you only need to run <code>./update-dhcp-dns</code> once. The modifications
introduced by this script will persist across container restarts because we&rsquo;re
using a Docker volume in Pihole&rsquo;s service definition.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>This is actually done in two steps: the client sends a lease request to
the server, then the server replies with an acknowledgement.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>If this environment variable is not set, Pihole will generate a random
password. In this case, you&rsquo;ll need to check the logs and search for the word
&ldquo;random&rdquo; in order to find the generated password.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    
  </channel>
</rss>
