<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Amel Spahić</title>
    <description>The latest articles on DEV Community by Amel Spahić (@amelspahic).</description>
    <link>https://dev.to/amelspahic</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F350365%2Fdfa26b4e-d210-448d-9ac8-5d4aef7f75ac.jpg</url>
      <title>DEV Community: Amel Spahić</title>
      <link>https://dev.to/amelspahic</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/amelspahic"/>
    <language>en</language>
    <item>
      <title>How to create and publish a TypeScript library with ease</title>
      <dc:creator>Amel Spahić</dc:creator>
      <pubDate>Tue, 07 Mar 2023 22:57:23 +0000</pubDate>
      <link>https://dev.to/amelspahic/how-to-create-and-publish-a-typescript-library-with-ease-29j9</link>
      <guid>https://dev.to/amelspahic/how-to-create-and-publish-a-typescript-library-with-ease-29j9</guid>
      <description>&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/amelspahic" rel="noopener noreferrer"&gt;
        amelspahic
      &lt;/a&gt; / &lt;a href="https://github.com/amelspahic/typescript-library-template" rel="noopener noreferrer"&gt;
        typescript-library-template
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Initial boilerplate for TypeScript libraries including tests, coverage, documentation.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;typescript-library-template&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;A starter template for TypeScript libraries. Use this repository as a starting point for your own TypeScript library. This template includes the following features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Compiles TypeScript code using both the &lt;code&gt;tsconfig.json&lt;/code&gt; and &lt;code&gt;tsconfig.module.json&lt;/code&gt; files.&lt;/li&gt;
&lt;li&gt;Formats TypeScript code using &lt;a href="https://prettier.io" rel="nofollow noopener noreferrer"&gt;Prettier&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Lints TypeScript code using &lt;a href="https://eslint.org" rel="nofollow noopener noreferrer"&gt;ESLint&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Runs unit tests using &lt;a href="https://github.com/avajs/ava" rel="noopener noreferrer"&gt;AVA&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Generates code coverage reports using NYC.&lt;/li&gt;
&lt;li&gt;Generates HTML documentation using &lt;a href="https://typedoc.org" rel="nofollow noopener noreferrer"&gt;TypeDoc&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Uses &lt;a href="https://github.com/typicode/husky" rel="noopener noreferrer"&gt;Husky&lt;/a&gt; Git hooks and &lt;a href="https://github.com/okonet/lint-staged" rel="noopener noreferrer"&gt;Lint-staged&lt;/a&gt; pre-commit hooks.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Installation&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Clone the repository:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;git clone https://github.com/amelspahic/typescript-library-template.git&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Install the dependencies:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;npm install&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;There are several scripts available to help you get started:&lt;/p&gt;

&lt;p&gt;Compile the TypeScript code using both the &lt;code&gt;tsconfig.json&lt;/code&gt; and &lt;code&gt;tsconfig.module.json&lt;/code&gt; files.&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;npm run build&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Formats the TypeScript code using Prettier and lints the code using ESLint, fixing any issues found.&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;npm run fix&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Lints the TypeScript code using ESLint, checks the code formatting using Prettier, and runs the unit tests using AVA.&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;npm run &lt;span class="pl-c1"&gt;test&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;…&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/amelspahic/typescript-library-template" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;&lt;a href="https://www.typescriptlang.org/" rel="noopener noreferrer"&gt;TypeScript&lt;/a&gt; is a popular and influential language that extends JavaScript with static typing and other features. It can help you write more reliable, maintainable, scalable code for your projects. However, creating and publishing a TypeScript library can be challenging, especially if you are new to the TypeScript ecosystem. Many tools and configurations are involved, and finding a good template or guide that covers everything you need is challenging.&lt;/p&gt;

&lt;p&gt;For the reasons mentioned above, I created a TypeScript library template you can use as a starting point for your library. It is based on my experience and best practices from the TypeScript community (this is usually subjective), and it includes everything you need for developing, testing, documenting, and publishing your library. This article will explain what this template offers, how to use it, and how to customize it for your needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is typescript-library-template?
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://github.com/amelspahic/typescript-library-template" rel="noopener noreferrer"&gt;typescript-library-template&lt;/a&gt; is a GitHub repository that you can use as a template for creating your own TypeScript library. It has the following features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;TypeScript: as the primary language for writing your library code. It also includes some recommended settings for the &lt;code&gt;tsconfig.json&lt;/code&gt; and &lt;code&gt;tslint.json&lt;/code&gt; files.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;AVA: as the testing framework for writing and running unit tests for your library. It also supports code coverage reports with Istanbul (NYC).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Typedoc: as the documentation generator for creating API documentation for your library from your TypeScript comments.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Prettier: as the code formatter for keeping your code style consistent and readable.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;ESLint (typescript-eslint): for static code analysis.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Husky and Lint-staged: The template uses Husky and Lint-staged to run pre-commit hooks that ensure your code is formatted, linted, tested, and documented before committing.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to use typescript-library-template?
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;I will mention just a couple of commands below. You can find more information in the README.md file.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Using &lt;a href="https://github.com/amelspahic/typescript-library-template" rel="noopener noreferrer"&gt;typescript-library-template&lt;/a&gt; is very easy. You need to follow these steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Click the "Use this template" button on the GitHub repository page. This will create a new repository based on this template under your account.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Clone or download your new repository to your local machine.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run &lt;code&gt;npm install&lt;/code&gt; or &lt;code&gt;yarn install&lt;/code&gt; to install all the dependencies.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Start developing your library by editing the &lt;code&gt;src/index.ts&lt;/code&gt; file (or adding more files under &lt;code&gt;src&lt;/code&gt;). Using AVA syntax, you can write unit tests using &lt;code&gt;.spec.ts&lt;/code&gt; or &lt;code&gt;.test.ts&lt;/code&gt; suffixes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run &lt;code&gt;npm run build&lt;/code&gt; or &lt;code&gt;yarn build&lt;/code&gt; to build your library. This will generate multiple output files under the &lt;code&gt;dist&lt;/code&gt; folder.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run the &lt;code&gt;npm run test&lt;/code&gt; or &lt;code&gt;yarn test&lt;/code&gt; to run all unit tests using AVA.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;To generate a code coverage report under the coverage folder, run &lt;code&gt;npm run cov&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run &lt;code&gt;npm run doc&lt;/code&gt; or &lt;code&gt;yarn doc&lt;/code&gt; to generate API documentation using Typedoc. This will create an HTML file under &lt;code&gt;docs/index.html&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run &lt;code&gt;npm run fix&lt;/code&gt; or &lt;code&gt;yarn fix&lt;/code&gt; to format all source files using Prettier.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Commit your changes (it will activate a pre-commit hook that will perform test and linting/formatting)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Push your changes to GitHub.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to customize typescript-library-template?
&lt;/h2&gt;

&lt;p&gt;You can customize the typescript-library-template according to your preferences. Add files, change configurations, and remove whatever you find unimportant to your liking.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>javascript</category>
      <category>github</category>
      <category>library</category>
    </item>
    <item>
      <title>Geoguessr exploit with proxy (Fiddler on Windows)</title>
      <dc:creator>Amel Spahić</dc:creator>
      <pubDate>Thu, 27 Oct 2022 19:53:40 +0000</pubDate>
      <link>https://dev.to/amelspahic/geoguessr-exploit-with-proxy-fiddler-on-windows-30ak</link>
      <guid>https://dev.to/amelspahic/geoguessr-exploit-with-proxy-fiddler-on-windows-30ak</guid>
      <description>&lt;p&gt;There is a popular (and incredible) geographical game called &lt;a href="https://www.geoguessr.com/"&gt;Geoguessr&lt;/a&gt;, where players guess locations from Google Maps Street View imagery. I play it regularly, and I am sure many of you have seen those guys who can &lt;strong&gt;&lt;em&gt;guess&lt;/em&gt;&lt;/strong&gt; where they are just by a quick glimpse of the part of the grass or sky or just pixelated images. Well, you can do it, too. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Avoid using this method&lt;/strong&gt;, but I would like to know about things in the real world.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  How Geoguessr works?
&lt;/h2&gt;

&lt;p&gt;As with any other web application, Geoguessr is a client-server application with client-side and server-side parts. The simplified explanation would be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Through the application's user interface, the user sends a request to the web server across the Internet.&lt;/li&gt;
&lt;li&gt;The web server forwards this request to the web application server.&lt;/li&gt;
&lt;li&gt;After completing the specified task, the web application server generates the necessary data results.&lt;/li&gt;
&lt;li&gt;The web application server then relays those results to the web server (requested information or processed data).&lt;/li&gt;
&lt;li&gt;The client receives the requested information from the web server (tablet, mobile device, or desktop).&lt;/li&gt;
&lt;li&gt;The requested information appears on the user's display.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dPrgdD27--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3vk2uaaft7vbxaz8zfvu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dPrgdD27--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3vk2uaaft7vbxaz8zfvu.png" alt="HTTPS communication" width="797" height="145"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's take, for example, a &lt;strong&gt;Classic Mode&lt;/strong&gt; and find your map (World, Famous Places, United States, etc.). When you start a game, a browser will send an HTTP request to the endpoint: &lt;code&gt;https://www.geoguessr.com/api/v3/games&lt;/code&gt;. The response will return the current location's &lt;code&gt;latitude&lt;/code&gt; and &lt;code&gt;longitude&lt;/code&gt;. That easy!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For different playing modes, the game will call other endpoints, e.q., &lt;strong&gt;Streaks&lt;/strong&gt; will call &lt;code&gt;https://www.geoguessr.com/api/v3/games/streak&lt;/code&gt;. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---c5IoLTA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1666878244347/lqa4yPd5C.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---c5IoLTA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1666878244347/lqa4yPd5C.png" alt="Geoguessr game" width="880" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This begs the question: Well, we already have the information about the location; how to make something out of it without suffering? We can manually search for the &lt;br&gt;
&lt;code&gt;latitude&lt;/code&gt; and &lt;code&gt;longitude&lt;/code&gt; on &lt;a href="https://www.google.com/maps"&gt;Google Maps&lt;/a&gt;, but it is time-consuming and slow. There is a better idea, and we call it  - an &lt;strong&gt;INTERMEDIARY (Proxy)&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Proxy?
&lt;/h2&gt;

&lt;p&gt;There is no better explanation than in &lt;a href="https://www.rfc-editor.org/rfc/rfc7230#section-2.3"&gt;Section 2.3 of RFC 7230&lt;/a&gt; (Request for Comments) of the Internet Engineering Task Force (IETF).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A "proxy" is a message-forwarding agent that is selected by the&lt;br&gt;
   client, usually via local configuration rules, to receive requests&lt;br&gt;
   for some type(s) of absolute URI and attempt to satisfy those&lt;br&gt;
   requests via translation through the HTTP interface. Some&lt;br&gt;
   translations are minimal, such as for proxy requests for "http" URIs,&lt;br&gt;
   whereas other requests might require translation to and from entirely&lt;br&gt;
   different application-level protocols. Proxies are often used to&lt;br&gt;
   group an organization's HTTP requests through a common intermediary&lt;br&gt;
   for the sake of security, annotation services, or shared caching.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Rbyv4BVB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1666858636687/4ZsHauEEk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Rbyv4BVB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1666858636687/4ZsHauEEk.png" alt="http_proxy.png" width="787" height="282"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Fiddler Classic
&lt;/h2&gt;

&lt;p&gt;We want something easy to work with, easily configurable, and accessible. This is not a sponsored blog post, but &lt;a href="https://www.telerik.com/fiddler/fiddler-classic"&gt;Fiddler Classic&lt;/a&gt; is one of the best tools of this kind for Windows I have been working with.&lt;/p&gt;

&lt;p&gt;Fiddler Classic is a specialized proxy server tool for troubleshooting online traffic from programs like browsers. It records and collects online traffic before sending it to a web server. The replies from the server are then sent back to the Fiddler program, which subsequently sends them back to the client. Fiddler's web debugging user interface shows the captured web traffic through a session list.&lt;/p&gt;
&lt;h3&gt;
  
  
  Capture and decrypt HTTPS traffic
&lt;/h3&gt;

&lt;p&gt;By default, Fiddler Classic does not capture and decrypt secure HTTPS traffic. We need to enable HTTPS traffic decryption to capture data sent through HTTPS.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to &lt;strong&gt;Tools&lt;/strong&gt; &amp;gt; &lt;strong&gt;Options&lt;/strong&gt; &amp;gt; &lt;strong&gt;HTTPS&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Ensure that &lt;strong&gt;Capture HTTPS CONNECTs&lt;/strong&gt; and &lt;strong&gt;Decrypt HTTPS Traffic&lt;/strong&gt; are selected&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8tMVCacM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1666877587850/VXyB2iL82.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8tMVCacM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1666877587850/VXyB2iL82.png" alt="image.png" width="566" height="369"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we can track HTTPS requests made from our browsers. Here is an example of Geoguessr game invocation, the same results as those from the browser image above:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--IQhpwW62--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1666876546105/lMickkCYO.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--IQhpwW62--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1666876546105/lMickkCYO.png" alt="image.png" width="880" height="528"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  FiddlerScript
&lt;/h3&gt;

&lt;p&gt;One of Fiddler's most powerful features is &lt;a href="https://www.telerik.com/blogs/understanding-fiddlerscript"&gt;FiddlerScript&lt;/a&gt;, which enables us to alter requests and responses "on the fly" to implement any desired action. It is built on JScript.NET, a.NET version of JavaScript, making it simple for web developers to use. Most .NET developers can design basic rules using FiddlerScript because the syntax is similar enough to C#.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to &lt;strong&gt;Rules&lt;/strong&gt; &amp;gt; &lt;strong&gt;Customize Rules...&lt;/strong&gt;, and it will open up the FiddlerScript editor (or Notepad if you don't have an editor installed)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--B95o5kFa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1666878570594/wRUylu4yX.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--B95o5kFa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1666878570594/wRUylu4yX.png" alt="image.png" width="880" height="549"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Many things are happening in that file, but we will focus on the &lt;code&gt;OnBeforeResponse&lt;/code&gt; session event method. It fires after the complete response has been read from the server and before the response is returned to the client (unless Streaming is enabled), so we can add some behavior.&lt;/p&gt;
&lt;h4&gt;
  
  
  Open Microsoft Edge pinned to the location
&lt;/h4&gt;

&lt;p&gt;Once we start playing the game, we want our FiddlerScript to run a new browser session with the location taken from the response pinned on a map.&lt;/p&gt;

&lt;p&gt;To retrieve &lt;code&gt;latitude&lt;/code&gt; and &lt;code&gt;longitude&lt;/code&gt; from the JSON response, we need to do some JSON decoding and get the latest result from the &lt;code&gt;rounds&lt;/code&gt; parameter (the parameter &lt;code&gt;rounds&lt;/code&gt; is an array of the current and previous results). &lt;/p&gt;

&lt;p&gt;The only thing left now is to launch a fresh browser session (or another tab) if you launch the same browser as your current one. I use the Brave browser and launch Microsoft Edge (because it is on Windows by default). Our final &lt;code&gt;OnBeforeResponse&lt;/code&gt; event will look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;OnBeforeResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;oSession&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Session&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m_Hide304s&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;oSession&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;responseCode&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="m"&gt;304&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;oSession&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"ui-hide"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Only GET method, otherwise, it will open the browser again once we POST the results&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;oSession&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RequestMethod&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"GET"&lt;/span&gt; 
        &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;oSession&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uriContains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api/v3/games/"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
        &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;oSession&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uriContains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api/v3/challenges/"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;oSession&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uriContains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api/battle-royale/"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt; 
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;latLng&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;oSession&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetResponseBodyAsString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;oJson&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Fiddler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WebFormats&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;JsonDecode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;latLng&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;rs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;oJson&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JSONObject&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"rounds"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;lat&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;  &lt;span class="n"&gt;rs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;rs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s"&gt;"lat"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;lng&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;rs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s"&gt;"lng"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;zoomLevel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"8z"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="n"&gt;Utilities&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LaunchHyperlink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"microsoft-edge:https://maps.google.com/maps/place/"&lt;/span&gt;&lt;span class="p"&gt;+&lt;/span&gt;&lt;span class="n"&gt;lat&lt;/span&gt;&lt;span class="p"&gt;+&lt;/span&gt;&lt;span class="s"&gt;","&lt;/span&gt;&lt;span class="p"&gt;+&lt;/span&gt;&lt;span class="n"&gt;lng&lt;/span&gt;&lt;span class="p"&gt;+&lt;/span&gt;&lt;span class="s"&gt;"/@"&lt;/span&gt;&lt;span class="p"&gt;+&lt;/span&gt;&lt;span class="n"&gt;lat&lt;/span&gt;&lt;span class="p"&gt;+&lt;/span&gt;&lt;span class="s"&gt;","&lt;/span&gt;&lt;span class="p"&gt;+&lt;/span&gt;&lt;span class="n"&gt;lng&lt;/span&gt;&lt;span class="p"&gt;+&lt;/span&gt;&lt;span class="s"&gt;","&lt;/span&gt;&lt;span class="p"&gt;+&lt;/span&gt;&lt;span class="n"&gt;zoomLevel&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, you need to keep FiddlerClassic open for it to act as a proxy. See it in action:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/tQS0U7K5L4M"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Docker Persistence feat. MS SQL Server, PostgreSQL, MariaDB, MySQL, MongoDB</title>
      <dc:creator>Amel Spahić</dc:creator>
      <pubDate>Sat, 21 May 2022 18:59:00 +0000</pubDate>
      <link>https://dev.to/amelspahic/docker-persistence-feat-ms-sql-server-postgresql-mariadb-mysql-mongodb-pl6</link>
      <guid>https://dev.to/amelspahic/docker-persistence-feat-ms-sql-server-postgresql-mariadb-mysql-mongodb-pl6</guid>
      <description>&lt;p&gt;We did the initial project setup on containers in the &lt;a href="https://dev.to/amelspahic/demystifying-docker-net-6-on-docker-docker-debugging-4o0h"&gt;previous&lt;/a&gt; "Demystifying Docker" series article. In this one, we will cover the database with persistence (examples with MS SQL Server, PostgreSQL, MySQL, MariaDB, Mongo) part. Still, first, we need to clarify a couple of things regarding the concept of the critical services (such as the database) running in containers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why NOT database in Docker?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;TL;DR: Carefully consider using it in the production environment if your database is a critical service.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Docker was not created with persistence in mind, and one of the most appealing features of containers is their ability to be started and terminated at will. The data in the container is transient, and it is erased with the container. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Be careful using database services in containers in a PRODUCTION environment! If your database is not a critical service (which is rare), maybe you could try it in Docker; otherwise, you are entering a dangerous zone of having a headache in the (probably near) future.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you are deploying on a cloud, the best option is to use cloud database services (AWS Relational Database Service (RDS), Azure Databases, Google Cloud Databases, Managed Databases on Digital Ocean, or an equivalent hosted database service of your cloud provider). This will simplify many management tasks such as updating minor versions, handling regular backups and even scaling up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why YES database in Docker?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;TL;DR: For any environment (production with particular caution).&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let us suppose that we have a couple of different database engines in our projects, or have different versions of any (or all) of them, or your colleague has another operating system or with a different setup, or you need a quick start on the new developer joining the team, we can mention a lot more, but I am sure you get the point. You don't want to overload your working machine with different installations and versions when you can do it in one place easily through the &lt;code&gt;docker&lt;/code&gt; CLI commands or the &lt;code&gt;docker-compose&lt;/code&gt; specification. You can add or remove it without impacting the rest of the system.&lt;/p&gt;

&lt;p&gt;As previously mentioned, containers don't exist with persistence in mind, but we usually want to have data even if we restart our container engine or working machine. Luckily, there are a couple of ways to preserve the data mentioned below.&lt;/p&gt;

&lt;h2&gt;
  
  
  Data persistence
&lt;/h2&gt;

&lt;p&gt;There are two ways of Docker handling the persistence in general - volumes and bind mounts. Both allow you to mount a location on the host machine to a place in the container. This provides storage for the data even if the container is shut down, and there is no need to worry about the data being lost.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bind mounts
&lt;/h3&gt;

&lt;p&gt;Bind mount is &lt;strong&gt;not my preferred&lt;/strong&gt; way of persisting data, but it still has its usage, so I will briefly run through the idea with PostgreSQL as an example. Bind mounts will mount a file or directory to the container from the host machine, and then it can be referenced via its absolute path. It relies on the host machine's filesystem having a specific directory structure. If not, it is necessary to explicitly create a path to the file or folder to place the storage. Additionally, bind mounts give us access to sensitive files, and we can change the host filesystem through processes running in a container, which is considered a &lt;strong&gt;security implication&lt;/strong&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Usage
&lt;/h4&gt;

&lt;p&gt;You can use the flags &lt;code&gt;--mount&lt;/code&gt; and &lt;code&gt;-v&lt;/code&gt; to use bind mounts on a container. The most noticeable difference between the two options is that &lt;code&gt;--mount&lt;/code&gt; is more explicit and verbose, whereas &lt;code&gt;-v&lt;/code&gt; is more of a shortcut for &lt;code&gt;--mount&lt;/code&gt;. It combines all of the &lt;code&gt;--mount&lt;/code&gt; options into a single field. To learn more, always reference the &lt;a href="https://docs.docker.com/storage/bind-mounts/" rel="noopener noreferrer"&gt;official documentation&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker run --rm --name pgdb -e POSTGRES_PASSWORD=somepass --mount type=bind,source="$(pwd)",target=/var/lib/postgresql/data -p 2000:5432 -d postgres
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Commands explanation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;docker run&lt;/code&gt; creates a writeable container layer over the specified image and then starts it using the selected command&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--rm&lt;/code&gt; will automatically remove the container when it exits&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--name pgdb&lt;/code&gt; assigns container name &lt;em&gt;pgdb&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-e POSTGRES_PASSWORD=somepass&lt;/code&gt; sets the required environment variable to use the PostgreSQL image, and it must not be empty or undefined. This environment variable specifies the superuser password for PostgreSQL.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--mount type=bind,source="$(pwd)",target=/var/lib/postgresql/data&lt;/code&gt; creates the &lt;code&gt;type=bind&lt;/code&gt; mount between the current directory (&lt;code&gt;pwd&lt;/code&gt; returns a current working directory name) and the default directory for the database files in Docker container &lt;code&gt;/var/lib/postgresql/data&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-p 2000:5432&lt;/code&gt; maps the default PostgreSQL port 5432 to the host port 2000 (&lt;em&gt;later, we will use port 2000 to connect to the database instance from the host machine&lt;/em&gt;). You can change &lt;code&gt;port 2000&lt;/code&gt; to some other port if that one is already in use.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-d&lt;/code&gt; will run the command in the detached mode (it won't lock our terminal). By design, containers started in detached mode exit when the root process used to run the container exits, unless you specify the --rm option. If you use -d with --rm, the container is removed when it exits or when the daemon exits, whichever happens first.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;postgres&lt;/code&gt; is the base PostgreSQL image&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1650759092681%2FBanTu254o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1650759092681%2FBanTu254o.png" alt="image.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our PostgreSQL container is running, and now it is accessible from the appropriate database tool (such as &lt;a href="https://dbeaver.io/" rel="noopener noreferrer"&gt;DBeaver Community&lt;/a&gt;). I am using port &lt;code&gt;2000&lt;/code&gt; and password &lt;code&gt;somepass&lt;/code&gt; to connect to our database instance, as we defined earlier in the Docker CLI command.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcr4lf3e6dr5nj54l2gb5.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcr4lf3e6dr5nj54l2gb5.gif" alt="PostgreSQL running on Docker"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My current &lt;code&gt;working directory&lt;/code&gt; is &lt;code&gt;~/testmount&lt;/code&gt;, and running the previous Docker command will create the necessary database files in that folder.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1650760842668%2FsQIP6SXon.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1650760842668%2FsQIP6SXon.png" alt="image.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Volume mounts (preferred way)
&lt;/h3&gt;

&lt;p&gt;Docker fully manages docker volumes; therefore, they are unaffected by our directory structure or the host machine's operating system. When we utilize a &lt;em&gt;volume&lt;/em&gt;, Docker creates a new directory in the host machine's storage directory, and Docker handles its content. For Docker volumes, storage is not associated with the container's life cycle and resides outside the container. &lt;/p&gt;

&lt;p&gt;Some of the advantages: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;kill containers as you need and still retain your data&lt;/li&gt;
&lt;li&gt;attach volumes to multiple containers running at the same time&lt;/li&gt;
&lt;li&gt;reuse storage across multiple containers (for example, one container writes to storage and another reads from storage)&lt;/li&gt;
&lt;li&gt;volumes do not increase the size of the Docker containers that use them&lt;/li&gt;
&lt;li&gt;you can use the Docker CLI to manage your volumes (for example, retrieving a list of volumes or deleting unused volumes).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All commands below follow the same pattern because we use persistence for the container unrelated to which database management system we use. The only things different are environment variables and target directories (because other databases usually use different file locations).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; &amp;lt;custom-name&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-e&lt;/span&gt; &amp;lt;&lt;span class="nv"&gt;ENV_VAR_1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;some_value_1&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-e&lt;/span&gt; &amp;lt;&lt;span class="nv"&gt;ENV_VAR_2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;some_value_2&amp;gt; &lt;span class="se"&gt;\ &lt;/span&gt;
&lt;span class="nt"&gt;--mount&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;volume,source&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;custom-source-name&amp;gt;,target&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;database-file-location-in-container&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-p&lt;/span&gt; &amp;lt;host-port&amp;gt;:&amp;lt;container-database-port&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-d&lt;/span&gt; &amp;lt;docker-image-with-or-without-tag&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Commands explanation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;docker run&lt;/code&gt; creates a writeable container layer over the specified image and then starts it using the selected command&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--rm&lt;/code&gt; will automatically remove the container when it exits&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--name custom-name&lt;/code&gt; assigns container name &lt;em&gt;custom-name&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-e ENV_VAR_1=some_value_1 -e ENV_VAR_2=some_value_2&lt;/code&gt; sets the required environment variables to use for the database image. Different database management systems require different environment variables. You can find those in the documentation for the specific DBMS.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--mount type=volume,source=custom-source-name,target=database-file-location-in-container&lt;/code&gt; creates the &lt;code&gt;type=volume&lt;/code&gt; with name &lt;strong&gt;custom-source-name&lt;/strong&gt; and mounts it to the default directory for the database files in Docker container &lt;code&gt;database-file-location-in-container&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-p host-port:container-database-port&lt;/code&gt; maps the default database port (i.e., 5432 for PostgreSQL, 1433 for SQL Server, 3306 for MariaDB and MySQL, etc.). You can change the &lt;code&gt;host-port&lt;/code&gt; to some free port on your host machine.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-d&lt;/code&gt; will run the command in the detached mode (it won't lock our terminal). By design, containers started in detached mode exit when the root process used to run the container exits, unless you specify the --rm option. If you use -d with --rm, the container is removed when it exits or when the daemon exits, whichever happens first.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docker-image-with-or-without-version&lt;/code&gt; is the base Docker image from the Docker Hub.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  SQL Server
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker run --rm --name our-mssql -e "ACCEPT_EULA=Y" -e "SA_PASSWORD=Str0ngP@ssword" -e "MSSQL_PID=Express" --mount type=volume,source=custommssql,target=/var/opt/mssql -p 7000:1433 -d mcr.microsoft.com/mssql/server:2019-latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Environment variables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ACCEPT_EULA&lt;/code&gt; confirms your acceptance of the End-User Licensing Agreement.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SA_PASSWORD&lt;/code&gt; is the database system administrator (userid = 'sa') password used to connect to SQL Server once the container is running. Important note: This password needs to include at least 8 characters of at least three of these four categories: uppercase letters, lowercase letters, numbers, and non-alphanumeric symbols.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;MSSQL_PID&lt;/code&gt; is the Product ID (PID) or Edition that the container will run with. Acceptable values: &lt;strong&gt;Developer&lt;/strong&gt; (this is the default if no MSSQL_PID environment variable is supplied), &lt;strong&gt;Express&lt;/strong&gt;, &lt;strong&gt;Standard&lt;/strong&gt;, &lt;strong&gt;Enterprise&lt;/strong&gt;, &lt;strong&gt;EnterpriseCore&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A complete list of environment variables can be found &lt;a href="https://docs.microsoft.com/en-us/sql/linux/sql-server-linux-configure-environment-variables?view=sql-server-2017" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Connect using information defined above&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Server Name - &lt;code&gt;127.0.0.1,7000&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Authentication - &lt;code&gt;SQL Server Authentication&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Username - &lt;code&gt;sa&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Password - &lt;code&gt;Str0ngP@ssword&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1653140530423%2FD9UUbipDF.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1653140530423%2FD9UUbipDF.gif" alt="2022-05-21_15-41-47.gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  PostgreSQL
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker run --rm --name our-postgresql -e POSTGRES_PASSWORD=Str0ngP@ssword --mount type=volume,source=custompostgres,target=/var/lib/postgresql/data -p 7001:5432 -d postgres:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Environment variables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;POSTGRES_PASSWORD&lt;/code&gt; - This environment variable is required for you to use the PostgreSQL image. It must not be empty or undefined. This environment variable sets the superuser password for PostgreSQL. The default superuser is defined by the POSTGRES_USER environment variable (if not present, the default user is &lt;code&gt;postgres&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Complete list of environment variables can be found &lt;a href="https://hub.docker.com/_/postgres" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Connect using information defined above&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Server Host - &lt;code&gt;localhost&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Port - &lt;code&gt;7001&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Username - &lt;code&gt;postgres&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Password - &lt;code&gt;Str0ngP@ssword&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1653140761260%2FZ5EcPx4c0.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1653140761260%2FZ5EcPx4c0.gif" alt="2022-05-21_15-45-41.gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  MariaDB
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker run --rm --name our-mariadb -e MARIADB_ROOT_PASSWORD=Str0ngP@ssword --mount  type=volume,source=custommariadb,target=/var/lib/mysql -p 7002:3306 -d mariadb:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Environment variables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;MARIADB_ROOT_PASSWORD&lt;/code&gt; specifies the password that will be set for the MariaDB &lt;code&gt;root&lt;/code&gt; superuser account. In the above example, it was set to &lt;code&gt;Str0ngP@ssword&lt;/code&gt; password.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A complete list of environment variables can be found &lt;a href="https://hub.docker.com/_/mariadb" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Connect using information defined above&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Server Host - &lt;code&gt;localhost&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Port - &lt;code&gt;7002&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Username - &lt;code&gt;root&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Password - &lt;code&gt;Str0ngP@ssword&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1653140857370%2FNqTXoNdG3.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1653140857370%2FNqTXoNdG3.gif" alt="2022-05-21_15-47-14.gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  MySQL
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker run --rm --name our-mysql -e MYSQL_ROOT_PASSWORD=Str0ngP@ssword --mount  type=volume,source=custommysql,target=/var/lib/mysql -p 7003:3306 -d mysql:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Environment variables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;MYSQL_ROOT_PASSWORD&lt;/code&gt; specifies the password set for the MySQL &lt;code&gt;root&lt;/code&gt; superuser account. In the above example, it was set to the &lt;code&gt;Str0ngP@ssword&lt;/code&gt; password.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A complete list of environment variables can be found &lt;a href="https://hub.docker.com/_/mysql" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Connect using information defined above&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Server Host - &lt;code&gt;localhost&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Port - &lt;code&gt;7003&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Username - &lt;code&gt;root&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Password - &lt;code&gt;Str0ngP@ssword&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1653141051860%2FCaclurW4S.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1653141051860%2FCaclurW4S.gif" alt="2022-05-21_15-50-32.gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  MongoDB
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker run --rm --name our-mongo -e MONGO_INITDB_ROOT_USERNAME=someuser -e MONGO_INITDB_ROOT_PASSWORD=Str0ngP@ssword --mount type=volume,source=custommongo,target=/data/db -p 7004:27017 -d mongo:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Environment variables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;MONGO_INITDB_ROOT_USERNAME&lt;/code&gt; and  &lt;code&gt;MONGO_INITDB_ROOT_PASSWORD&lt;/code&gt; will create a new user and set that user's password. This user is created in the admin authentication database and given the root role, a &lt;code&gt;superuser&lt;/code&gt; role.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note: This image will also create a volume for &lt;code&gt;/data/configdb&lt;/code&gt;, so you will see an additional hex ID volume in your Docker volumes.&lt;/p&gt;

&lt;p&gt;Connect using information defined above&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;URI - &lt;code&gt;mongodb://someuser:Str0ngP@ssword@localhost:7004&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1653141654332%2F8n5mWdrF-.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1653141654332%2F8n5mWdrF-.gif" alt="2022-05-21_16-00-27.gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Volumes
&lt;/h3&gt;

&lt;p&gt;You can check your newly created volume with the command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker volume ls
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1653140283258%2FsyvciweN8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1653140283258%2FsyvciweN8.png" alt="image.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Or you can check your running Docker Desktop instance under the &lt;em&gt;Volumes&lt;/em&gt; tab.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1653140302824%2F62xYzv_NL.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1653140302824%2F62xYzv_NL.png" alt="image.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Docker Compose
&lt;/h3&gt;

&lt;p&gt;Every previously defined database management system can be run through the &lt;code&gt;docker-compose&lt;/code&gt; instead of &lt;code&gt;Docker CLI&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.4'&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:latest&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_PASSWORD=Str0ngP@ssword&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;7000:5432&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;custommssql:/var/lib/postgresql/data&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;custommssql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;local&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Which one to use?
&lt;/h2&gt;

&lt;p&gt;There are a few essential things to consider when determining whether to use volumes or bind mounts. You should utilize volumes if you want your storage or persistent layer to be fully controlled by Docker and accessed only through Docker containers and the Docker CLI.&lt;/p&gt;

&lt;p&gt;On the other hand, Bind mounts are the perfect solution for the task if you need complete control of the storage and plan on letting other processes besides Docker access or modify the storage layer.&lt;/p&gt;

&lt;p&gt;According to the Docker documentation, using volumes is the most straightforward approach to start persisting data in your Docker container. In general, bind mounts have additional restrictions.&lt;/p&gt;

&lt;p&gt;Based on what we've observed thus far, this conclusion is not surprising. If you're ever unsure how to persist data in your Docker containers, using volumes is a wise rule of thumb.&lt;/p&gt;

&lt;p&gt;A bind mount is distinguished because it can be accessed and modified by programs other than Docker. This can be advantageous for integrating Docker with other operations, but it can also be inconvenient if security is an issue.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;Although we used databases as an example, we described two ways to keep data on Docker containers. The following article will show how to add the database service to the already created application with &lt;code&gt;docker-compose&lt;/code&gt; and cover the integration part.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>database</category>
      <category>tutorial</category>
      <category>programming</category>
    </item>
    <item>
      <title>Demystifying Docker: .NET 6 on Docker + Docker Debugging</title>
      <dc:creator>Amel Spahić</dc:creator>
      <pubDate>Sat, 16 Apr 2022 18:23:35 +0000</pubDate>
      <link>https://dev.to/amelspahic/demystifying-docker-net-6-on-docker-docker-debugging-4o0h</link>
      <guid>https://dev.to/amelspahic/demystifying-docker-net-6-on-docker-docker-debugging-4o0h</guid>
      <description>&lt;p&gt;We start this series by building and debugging a .NET 6 Web API application in Docker containers. Many developers still avoid using Docker for development and debugging purposes, so I hope this will somehow reduce the gap of the unexplored. This is the first part, so come back later to check others where we add PostgreSQL with persistence, log to Elasticsearch, explore and visualize with Kibana to the existing setup, and much more.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This part of the tutorial will cover .NET 6, but the whole idea of containerized services is that we are not limited to just a particular technology. Instead of .NET, maybe you prefer writing applications in NodeJS or Python (in later examples), or you want to combine all these technologies, containers allow us to do so with the least effort.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What you will learn
&lt;/h2&gt;

&lt;p&gt;After this series, you should be able to understand the significance of containerization with Docker and apply new knowledge to your projects. Also, I will try to keep up with best practices as much as possible so you can produce better software.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before we can even support this kind of story, we need to ensure that our machine and operating system keep the minimum requirements for the Docker engine. &lt;br&gt;
A couple of things need to be installed on your machine before we start with this process:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dotnet.microsoft.com/en-us/download/dotnet/6.0"&gt;.NET 6 SDK&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/engine/install/"&gt;Docker Engine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://code.visualstudio.com/download"&gt;Visual Studio Code&lt;/a&gt; (You can use others, but this one is with most extensions)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Source
&lt;/h2&gt;

&lt;p&gt;You can find the source code on GitHub. Every part of this series will have a separate branch, and all of them will be merged into the master branch.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/amelspahic/blog-series/tree/part-1"&gt;https://github.com/amelspahic/blog-series/tree/part-1&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Why Docker
&lt;/h2&gt;

&lt;p&gt;Docker is a containerization technology for packaging your program and its dependencies into containers, ensuring that your application runs smoothly in any environment, including development, test, and production. Docker is a program that makes it easier to construct, deploy, and run containerized applications. With Docker in mind, the code running &lt;em&gt;on your machine&lt;/em&gt; will function the same on every other machine.&lt;/p&gt;
&lt;h2&gt;
  
  
  .NET 6 Web API
&lt;/h2&gt;

&lt;p&gt;Create a directory where we will hold everything related to our project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Create a new folder named `blog-series`
mkdir blog-series

# Change directory to the new directory
cd blog-series
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a .NET 6 Web API application using .NET CLI in the terminal (if you use Visual Studio, you can create a new project from the template).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotnet new webapi --no-https -o src/sample-app -n SampleAPI
cd src/sample-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Command explanation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;dotnet new webapi&lt;/code&gt; will create a new Web API project (check additional templates with &lt;code&gt;dotnet new --list&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--no-https&lt;/code&gt; will turn off HTTPS (I use reverse proxy configuration anyways)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-o src/sample-app&lt;/code&gt; will output created boilerplate in the &lt;code&gt;src/sample-app&lt;/code&gt; folder&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-n SampleAPI&lt;/code&gt; is the name of our project &lt;code&gt;SampleAPI.csproj&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;CLI will generate a sample boilerplate that we will use in this example. The project is ready to run, so for validation, run &lt;code&gt;dotnet run&lt;/code&gt; from the &lt;code&gt;src/sample-app&lt;/code&gt; folder (where we created our .NET 6 project).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotnet run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output will look like the image below, and it will show on which ports your application is available.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xr5HQxIr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1648672667668/yTBTXCRrR.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xr5HQxIr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1648672667668/yTBTXCRrR.png" alt="image.png" width="880" height="207"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can access the endpoint on the presented URL (mine is on &lt;code&gt;localhost&lt;/code&gt;, port &lt;code&gt;5130&lt;/code&gt;), &lt;code&gt;http://localhost:5130/WeatherForecast&lt;/code&gt; will get the sample response.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hJJG7o2d--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1648672581181/pPSdNZngc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hJJG7o2d--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1648672581181/pPSdNZngc.png" alt="image.png" width="880" height="178"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Visual Studio Code
&lt;/h3&gt;

&lt;p&gt;At the start, we created a new folder, &lt;code&gt;blog-series&lt;/code&gt; (maybe you named it differently), where we keep all our independent projects. Open it in Visual Studio Code.&lt;br&gt;
You will be prompted to add build and debug assets for C# - choose &lt;strong&gt;Yes&lt;/strong&gt; and VS Code will create another folder &lt;code&gt;.vscode&lt;/code&gt; in which &lt;strong&gt;debugging&lt;/strong&gt; and &lt;strong&gt;task&lt;/strong&gt; configurations are stored.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zRjZHYJJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1648665127915/WJ0AXsKbT.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zRjZHYJJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1648665127915/WJ0AXsKbT.png" alt="image.png" width="880" height="475"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you are not prompted to generate build and debug assets, you can do it manually through the Command Palette (&lt;code&gt;Ctrl+Shift+P&lt;/code&gt;) and run the &lt;code&gt;.NET: Generate Assets for Build and Debug&lt;/code&gt; command.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Application containerization
&lt;/h2&gt;

&lt;p&gt;Be sure to have the Docker extension installed so that we can use extension goodies. We could create our own, but we won't bother since this is a more convenient method.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--w0AimI94--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1648666393706/1VLcpaxvx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--w0AimI94--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1648666393706/1VLcpaxvx.png" alt="image.png" width="880" height="475"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This extension will expand VS Code Command Palette with additional options. Press &lt;code&gt;Ctrl+Shift+P&lt;/code&gt; to open the Command Palette and search for &lt;code&gt;Docker: Add Docker Files to Workspace...&lt;/code&gt; (start typing).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--sPM4VzBP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1648672838282/0vKjWTeAX.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--sPM4VzBP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1648672838282/0vKjWTeAX.png" alt="image.png" width="880" height="475"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You will be prompted with different options, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Select Application Platform&lt;/code&gt; - choose &lt;code&gt;.NET: ASP.NET Core&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Select Operating System&lt;/code&gt; - choose &lt;code&gt;Linux&lt;/code&gt; (this is the operating system of the container, not your machine, so definitely choose Linux)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Port&lt;/code&gt; - type in which container port your application will use (I prefer 5000)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Include optional Docker Compose files&lt;/code&gt; - choose &lt;code&gt;Yes&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The extension will create a couple of files and configurations: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Dockerfile&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.dockerignore&lt;/code&gt;, which contains files and directories patterns to be excluded from the context&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docker-compose.yml&lt;/code&gt; - a YAML file that defines the services and, with a single command, can spin everything up or tear it all down&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docker-compose.debug.yml&lt;/code&gt; - same as &lt;code&gt;docker-compose.yml&lt;/code&gt;, but with debugging configuration&lt;/li&gt;
&lt;li&gt;VS Code tasks for building and running the container (in both debug- and release configuration, four tasks in total), and a debugging configuration for launching the container in debug mode.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Dockerfile
&lt;/h3&gt;

&lt;p&gt;The previous step created a &lt;code&gt;Dockerfile&lt;/code&gt;, a text document that contains all the commands to assemble an image in our &lt;code&gt;src/sample-app&lt;/code&gt; folder. Visual Studio Code extension did a good job, but I added a comment above each step to better understand what happened.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Initialize a new build stage and set the Base Image 
FROM mcr.microsoft.com/dotnet/aspnet:6.0-focal AS base
# Set the working directory
WORKDIR /app
# Expose the port 5000
EXPOSE 5000
# Sets the environment variable
ENV ASPNETCORE_URLS=http://+:5000

# Creates a non-root user with an explicit UID and adds a permission to access the /app folder
# For more info, please refer to https://aka.ms/vscode-docker-dotnet-configure-containers
RUN adduser -u 5678 --disabled-password --gecos "" appuser &amp;amp;&amp;amp; chown -R appuser /app
USER appuser

# Initialize the new build stage and set the Build Image
FROM mcr.microsoft.com/dotnet/sdk:6.0-focal AS build
# Set the working directory for subsequent COPY and RUN commands
WORKDIR /src
# Copy the project file to the container filesystem /src folder
COPY ["src/sample-app/SampleAPI.csproj", "src/sample-app/"]
# Execute dotnet restore to restore dependencies specified in the .csproj file
RUN dotnet restore "src/sample-app/SampleAPI.csproj"
# Copy everything to the container filesystem
COPY . .
# Set the working directory for subsequent RUN commands
WORKDIR "/src/src/sample-app"
# Execute project build with all of its dependencies
RUN dotnet build "SampleAPI.csproj" -c Release -o /app/build

# Pick up where a previous build stage left off
FROM build AS publish
# Publish the application and its dependencies to a folder for deployment
# UseAppHost=false to disable the generation of the native executable.
RUN dotnet publish "SampleAPI.csproj" -c Release -o /app/publish /p:UseAppHost=false

# Pick up where a previous base stage left off
FROM base AS final
# Set the working directory for the subsequent COPY command
WORKDIR /app
# Copy from the publish image /app/publish directory to the container filesystem
COPY --from=publish /app/publish .
# Specify the command executed when the container is started
ENTRYPOINT ["dotnet", "SampleAPI.dll"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you are interested in more Docker references, please check the &lt;a href="https://docs.docker.com/engine/reference/builder/"&gt;official documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  .dockerignore
&lt;/h3&gt;

&lt;p&gt;Visual Studio Code Docker extension created a &lt;code&gt;.dockerignore&lt;/code&gt; file which contains files and directories patterns to be excluded from the build context. This helps avoid unnecessarily sending large or sensitive files and directories to the daemon and potentially adding them to images using &lt;code&gt;ADD&lt;/code&gt; or &lt;code&gt;COPY&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/bin
**/charts
**/docker-compose*
**/compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
README.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  docker-compose.yml and docker-compose.debug.yml
&lt;/h3&gt;

&lt;p&gt;Our project needs to run multiple containers, and while we can do that separately, we want to have them configured in one place. That is the perfect place to introduce &lt;a href="https://docs.docker.com/compose/"&gt;&lt;strong&gt;docker-compose&lt;/strong&gt;&lt;/a&gt;. Even in scenarios where we use a single container, using Docker Compose provides a tool-independent configuration in a way that a single Dockerfile does not. Configuration settings such as volume mounts for the container, port mappings, and environment variables can be declared in the docker-compose YML files.&lt;/p&gt;

&lt;p&gt;The Compose file is a YAML file defining services, networks, and volumes. Docker-compose and Docker-compose.debug are pretty much the same, with a slightly different setup for the debug configuration (i.e., setting the Development environment and attaching a debugger from the host machine).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.4"&lt;/span&gt;
&lt;span class="c1"&gt;# A service definition contains a configuration that is applied to each container&lt;/span&gt;
&lt;span class="c1"&gt;# started for that service, much like passing command-line parameters to docker run&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Define a service called 'sampleapi'&lt;/span&gt;
  &lt;span class="na"&gt;sampleapi&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Specify the image to start the container from&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sampleapi&lt;/span&gt;
    &lt;span class="c1"&gt;# Configuration options that are applied at build time.&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# Either a path to a directory containing a Dockerfile&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
      &lt;span class="c1"&gt;# Compose uses an alternate file to build with. A build path must also be specified.&lt;/span&gt;
      &lt;span class="na"&gt;dockerfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;src/sample-app/Dockerfile&lt;/span&gt;
    &lt;span class="c1"&gt;# Expose ports (HOST:CONTAINER)&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;5000:5000&lt;/span&gt;
    &lt;span class="c1"&gt;# Set environment variables&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ASPNETCORE_ENVIRONMENT=Development&lt;/span&gt;
    &lt;span class="c1"&gt;# Mount host directories to container directories&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;~/.vsdbg:/remote_debugger:rw&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Please check the official Docker Compose documentation for more details on &lt;a href="https://docs.docker.com/compose/compose-file/compose-file-v3/"&gt;docker-compose specifications&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Starting the application
&lt;/h2&gt;

&lt;p&gt;We have everything in place to spin up our application in Docker. We can do it in two ways using the command line or the Visual Studio Code Command Palette.&lt;/p&gt;

&lt;h3&gt;
  
  
  Command line
&lt;/h3&gt;

&lt;p&gt;From the root folder, run the following command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker-compose up -d
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Command explanation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;docker-compose up&lt;/code&gt; creates and starts containers defined in the &lt;code&gt;docker-compose.yml&lt;/code&gt; file&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-d&lt;/code&gt; detaches the execution from the terminal&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Wait for the process to complete:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---Sap8R_k--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1650125178056/dLY6fXGJr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---Sap8R_k--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1650125178056/dLY6fXGJr.png" alt="image.png" width="880" height="717"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The error on the image is expected since we do not have any images created&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Visual Studio Code Command Palette
&lt;/h3&gt;

&lt;p&gt;After the Docker extension installation, we have additional options in the Command Palette (&lt;code&gt;Ctrl+Shift+P&lt;/code&gt;). Pick which docker-compose file you want to run. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--SRMe-PFu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1648716476553/8wVJxTXml.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SRMe-PFu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1648716476553/8wVJxTXml.png" alt="image.png" width="880" height="465"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Another way is to right-click on &lt;code&gt;docker-compose.yml&lt;/code&gt; or &lt;code&gt;docker-compose.debug.yml&lt;/code&gt; and select &lt;code&gt;Compose Up&lt;/code&gt; to reproduce the same behavior as the command-line execution.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tZKfUezB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1648716059157/uYnNBqNc8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tZKfUezB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1648716059157/uYnNBqNc8.png" alt="image.png" width="880" height="567"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Running the commands above will create an image with a &lt;code&gt;sampleapi&lt;/code&gt; tag (image name from the &lt;code&gt;docker-compose.yml&lt;/code&gt;), (re)create, start, and attach to a service container.  You can run &lt;code&gt;docker image ls&lt;/code&gt; to check if the image is successfully created. It will list the images we have on our machine. Also, &lt;code&gt;docker ps&lt;/code&gt; will show running containers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UjAhztPp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1648715872053/60WLqBfmZ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UjAhztPp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1648715872053/60WLqBfmZ.png" alt="image.png" width="880" height="142"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our application should be up and running. We can access it on the &lt;code&gt;port&lt;/code&gt; configured in the docker-compose file (in my case, &lt;code&gt;5000&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--oGV96AF0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1648717435455/CZIVR3dEe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--oGV96AF0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1648717435455/CZIVR3dEe.png" alt="image.png" width="880" height="178"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Debugging in Docker
&lt;/h2&gt;

&lt;p&gt;If you came this far, you probably know how to use standard debugging techniques. Just act as if it is a typical application without container support.&lt;/p&gt;

&lt;p&gt;We are skipping the part of standard debugging techniques and jump straight to Docker debugging. This one is more interesting as it brings you closer to the &lt;strong&gt;real-world container behavior&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;From the Debug tab, select the &lt;code&gt;Configuration&lt;/code&gt; dropdown and select &lt;code&gt;Add Configuration&lt;/code&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_Ed39oox--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1648719828835/ZVHSnWld-.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_Ed39oox--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1648719828835/ZVHSnWld-.png" alt="image.png" width="880" height="389"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You will be prompted to choose a predefined configuration, so choose &lt;code&gt;Docker: .NET Core Attach (Preview)&lt;/code&gt;, and it will automatically add a predefined configuration to &lt;code&gt;launch.json&lt;/code&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fyO70GLm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1648719955009/NAysneQWn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fyO70GLm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1648719955009/NAysneQWn.png" alt="image.png" width="880" height="681"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Right-click on one of the &lt;code&gt;docker-compose*.yml&lt;/code&gt; files and select &lt;code&gt;Compose Up&lt;/code&gt;. If you go with the &lt;code&gt;docker-compose.yml&lt;/code&gt; (without &lt;code&gt;.debug&lt;/code&gt;), you will be asked to copy the debugger to the container. Choose &lt;strong&gt;Yes&lt;/strong&gt; to be able to debug. If you are going with &lt;code&gt;docker-compose.debug.yml&lt;/code&gt;, it will just spin up the service(s).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Open your browser and go to &lt;code&gt;http://localhost:5000/weatherforecast&lt;/code&gt;. You should get the expected response.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Attach the debugger - you will need to choose container group and container&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--au6elfoy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1648720581303/BxDetI5__.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--au6elfoy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1648720581303/BxDetI5__.png" alt="image.png" width="880" height="485"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add a breakpoint and refresh the &lt;code&gt;http://localhost:5000/weatherforecast&lt;/code&gt;. If everything is configured correctly, the debugger should attach to your breakpoint.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wTCubw8U--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1648720627601/E7ZavunCw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wTCubw8U--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1648720627601/E7ZavunCw.png" alt="image.png" width="880" height="507"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Reloading changes
&lt;/h3&gt;

&lt;p&gt;When changing your code, right-click on the &lt;code&gt;docker-compose*.yml&lt;/code&gt; file, choose &lt;code&gt;Compose Restart&lt;/code&gt;, and reattach the debugger.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5lTC4HSo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1648721993174/yNuYIM7Yo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5lTC4HSo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1648721993174/yNuYIM7Yo.png" alt="image.png" width="880" height="536"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;p&gt;In the next part, we will add database support to our project in the containerized environment. We will focus on PostgreSQL, but a similar approach can be used for many other database systems (the main difference is in the code implementation).&lt;/p&gt;

</description>
      <category>docker</category>
      <category>webdev</category>
      <category>tutorial</category>
      <category>dotnet</category>
    </item>
    <item>
      <title>Set up Dynamic DNS for Dynamic IP Addresses at Home (for free) + WireGuard Configuration</title>
      <dc:creator>Amel Spahić</dc:creator>
      <pubDate>Fri, 31 Dec 2021 14:34:47 +0000</pubDate>
      <link>https://dev.to/amelspahic/set-up-dynamic-dns-for-dynamic-ip-addresses-at-home-for-free-wireguard-configuration-381j</link>
      <guid>https://dev.to/amelspahic/set-up-dynamic-dns-for-dynamic-ip-addresses-at-home-for-free-wireguard-configuration-381j</guid>
      <description>&lt;p&gt;We already talked about how to &lt;a href="https://amelspahic.com/bring-your-home-network-anywhere-for-free-home-vpn-with-wireguard-on-raspberry-pi-pi-hole-ubuntu-server-20-04-lts" rel="noopener noreferrer"&gt;Bring Your Home Network Anywhere For Free - Home VPN with Wireguard on Raspberry Pi + Pi-hole (Ubuntu Server 20.04 LTS)&lt;/a&gt;. It is an awesome thing, especially if you have a static IP address, but if you are like most households in the world (including myself), your internet service provider (ISP) provides you with a dynamic IP address. It means that your home network's IP address changes frequently, even a couple of times a day, so your VPN connection configuration needs to be changed accordingly to keep up.&lt;/p&gt;

&lt;p&gt;To solve that, we will utilize the Dynamic DNS solution. &lt;/p&gt;

&lt;h2&gt;
  
  
  Dynamic DNS
&lt;/h2&gt;

&lt;p&gt;Dynamic DNS is a way of assigning a custom domain name that automatically updates even as the IP address changes. This system has been around long enough that there are workarounds for these kinds of issues. For the purpose of this post, I will use a free DDNS service -  &lt;a href="https://www.noip.com/" rel="noopener noreferrer"&gt;No-IP&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Go to the &lt;a href="https://www.noip.com/" rel="noopener noreferrer"&gt;No-IP&lt;/a&gt; website and choose some cool hostname and domain:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1640549922937%2FpZy00vliE.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1640549922937%2FpZy00vliE.png" alt="image.png"&gt;&lt;/a&gt;&lt;br&gt;
You are required to sign up, so just populate the required fields and complete the registration (email confirmation and stuff). Your hostname will be pre-populated and set up for the currently logged-in IP address. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you are not logged in from your home network, you need to change the address to point to your home network's IP address.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once you are done setting up (don't forget to add username, No-IP will complain), we can set up our router to ping No-IP servers whenever our IP address changes.&lt;/p&gt;
&lt;h2&gt;
  
  
  Router Set Up
&lt;/h2&gt;

&lt;p&gt;My home router is Technicolor CGA2121, but the configuration shouldn't be different for others, just browse a bit around through the configuration options.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1640550413166%2FidEpTh1JM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1640550413166%2FidEpTh1JM.png" alt="image.png"&gt;&lt;/a&gt;&lt;br&gt;
After this is done, our router should &lt;strong&gt;automatically call&lt;/strong&gt; No-IP API to change the pointing IP address.&lt;/p&gt;
&lt;h2&gt;
  
  
  WireGuard Configuration
&lt;/h2&gt;

&lt;p&gt;When we set up WireGuard on our Raspberry Pi in the &lt;a href="https://amelspahic.com/bring-your-home-network-anywhere-for-free-home-vpn-with-wireguard-on-raspberry-pi-pi-hole-ubuntu-server-20-04-lts" rel="noopener noreferrer"&gt;previous post&lt;/a&gt;, we selected the &lt;strong&gt;Public IP&lt;/strong&gt; option with the current home IP address.&lt;/p&gt;

&lt;p&gt;Current configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ cat /etc/pivpn/wireguard/setupVars.conf
PLAT=Ubuntu
OSCN=focal
USING_UFW=0
IPv4dev=eth0
install_user=ubuntu
install_home=/home/ubuntu
VPN=wireguard
pivpnPORT=51820
pivpnDNS1=10.6.0.1
pivpnDNS2=
pivpnHOST=&amp;lt;PUBLIC IP ADDRESS&amp;gt; # &amp;lt;- We will change this
INPUT_CHAIN_EDITED=0
FORWARD_CHAIN_EDITED=0
pivpnPROTO=udp
pivpnMTU=1420
pivpnDEV=wg0
pivpnNET=10.6.0.0
subnetClass=24
ALLOWED_IPS="0.0.0.0/0, ::0/0"
UNATTUPG=1
INSTALLED_PACKAGES=(net-tools iptables-persistent wireguard-tools qrencode)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enter the edit mode for &lt;code&gt;setupVars.conf&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ sudo nano /etc/pivpn/wireguard/setupVars.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Change &lt;code&gt;pivpnHOST&lt;/code&gt; to point to your DDNS hostname which you created on No-IP - &lt;code&gt;pivpnHOST=xxxx.ddns.net&lt;/code&gt;. Save with &lt;code&gt;Ctrl+X, Y&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;New clients you generate will use the new endpoint but you need to manually edit existing clients. &lt;br&gt;
Open your configuration, for example, &lt;code&gt;amel.conf&lt;/code&gt;, and update the line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Endpoint = xxxx.ddns.net:51820
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Final Words
&lt;/h2&gt;

&lt;p&gt;I hope this tutorial will help you overcome issues with dynamic IP and help you improve your online being.&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>linux</category>
      <category>ddns</category>
    </item>
    <item>
      <title>Deploy .NET 6 Web Application With GitHub Actions To Self-Hosted Linux Machine (Virtual Private Server, Raspberry Pi, etc.)</title>
      <dc:creator>Amel Spahić</dc:creator>
      <pubDate>Fri, 31 Dec 2021 12:37:38 +0000</pubDate>
      <link>https://dev.to/amelspahic/deploy-net-6-application-with-github-actions-to-self-hosted-linux-machine-virtual-private-server-raspberry-pi-etc-2547</link>
      <guid>https://dev.to/amelspahic/deploy-net-6-application-with-github-actions-to-self-hosted-linux-machine-virtual-private-server-raspberry-pi-etc-2547</guid>
      <description>&lt;p&gt;Let's suppose you are that kind of developer that holds code on GitHub but deploys his fantastic private application manually to some Linux based Virtual Private Server (VPS) you pay $5/month, or to home Raspberry Pi (because why not). You are not alone; a lot of us do that. There is no issue there, but it can be a much better experience.&lt;/p&gt;

&lt;p&gt;More advanced users have some script to build to the output folder and then copy it to the server using  Secure File Transfer Protocol (SFTP). Not bad, but still not perfect. Different branches - different rules (unless you push everything directly to the &lt;strong&gt;main/master&lt;/strong&gt; branch. I won't tell anyone, but I know you, we will do the same here).&lt;/p&gt;

&lt;h2&gt;
  
  
  GitHub Actions
&lt;/h2&gt;

&lt;p&gt;GitHub Actions are a new type of CI/CD (Continuous Integration, Delivery) server that has been released by GitHub recently. They integrate closely with GitHub, and the GitHub repository can link GitHub Actions, so they will only run when the GitHub repo is updated.&lt;/p&gt;

&lt;p&gt;Running applications on self-hosted runners provides more control over the hardware, operating system, and software tools than GitHub-hosted runners. You may use self-hosted runners to build a customised hardware configuration with greater processing power or memory to execute bigger jobs, install programs available on your local network, and select an operating system that GitHub-hosted runners do not offer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The process
&lt;/h2&gt;

&lt;p&gt;I will be using Raspberry Pi with Ubuntu Server 20.04.3 LTS, but the same process we can perform on many other Linux distributions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Foreword
&lt;/h2&gt;

&lt;p&gt;If your application uses a local database to persist the data, that part is not covered here, but you can do it independently of this tutorial.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1 - Installing .NET 6.0 SDK
&lt;/h2&gt;

&lt;p&gt;There are always two parts of .NET applications - &lt;strong&gt;building&lt;/strong&gt; and &lt;strong&gt;running ** them. That's why when you visit  &lt;a href="https://dotnet.microsoft.com/en-us/download/dotnet/6.0" rel="noopener noreferrer"&gt;Download .NET 6.0 (Linux, macOS, and Windows)&lt;/a&gt;, you will see at least two things - **SDK&lt;/strong&gt; and &lt;strong&gt;Runtime&lt;/strong&gt;. The former includes everything you need to &lt;strong&gt;build and run&lt;/strong&gt; .NET Core applications, while the latter includes everything just to &lt;strong&gt;run&lt;/strong&gt; them.&lt;/p&gt;

&lt;p&gt;We will be &lt;strong&gt;building&lt;/strong&gt; and &lt;strong&gt;restoring&lt;/strong&gt; packages for our application on our machine, so we need to install the &lt;strong&gt;SDK&lt;/strong&gt; part. It also includes the &lt;strong&gt;Runtime&lt;/strong&gt;, so we don't need to install it separately.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;From Microsoft documentation: Package manager installs are only supported on the x64 architecture. Other architectures, such as ARM, must install .NET by some other means such as with Snap, an installer script, or a manual binary installation.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Case 1 - CPU on your machine IS x64
&lt;/h3&gt;

&lt;p&gt;If you have Linux operating system besides Ubuntu 20.04 LTS, you should visit &lt;a href="https://docs.microsoft.com/en-us/dotnet/core/install/linux" rel="noopener noreferrer"&gt;Install .NET on Linux Distributions | Microsoft Docs&lt;/a&gt; documentation and act accordingly. Installing the SDK on Ubuntu 20.04 LTS &lt;strong&gt;on x64 architecture&lt;/strong&gt; will need:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wget https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb &lt;span class="nt"&gt;-O&lt;/span&gt; packages-microsoft-prod.deb
&lt;span class="nb"&gt;sudo &lt;/span&gt;dpkg &lt;span class="nt"&gt;-i&lt;/span&gt; packages-microsoft-prod.deb
&lt;span class="nb"&gt;rm &lt;/span&gt;packages-microsoft-prod.deb

&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; apt-transport-https
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; dotnet-sdk-6.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This step installed all the necessary dependencies, and we are ready to go.&lt;/p&gt;

&lt;h3&gt;
  
  
  Case 2 - CPU on your machine IS NOT x64 (e.g. Raspberry Pi)
&lt;/h3&gt;

&lt;p&gt;There is a slightly different setup for Raspberry Pi because it has an ARM64 CPU. We need to install the Runtime manually or with a script - I prefer the script way (If you are a bit sceptical about the file, you can check the documentation &lt;a href="https://docs.microsoft.com/en-us/dotnet/core/install/linux-scripted-manual#scripted-install" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -sSL https://dot.net/v1/dotnet-install.sh | bash /dev/stdin -c 6.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wait for the process to complete, and the .NET SDK will put files in your current directory &lt;code&gt;~/.dotnet&lt;/code&gt;. To have &lt;code&gt;dotnet&lt;/code&gt; in our &lt;code&gt;PATH&lt;/code&gt; for a simple resolution, we can run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/.dotnet/dotnet"&lt;/span&gt; &lt;span class="s2"&gt;"/usr/bin/dotnet"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;If you don't want to add &lt;code&gt;dotnet&lt;/code&gt; to the PATH, you will need to invoke the &lt;code&gt;dotnet&lt;/code&gt; command from the directory &lt;code&gt;$HOME/.dotnet/dotnet&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Check if &lt;code&gt;dotnet&lt;/code&gt; installation completed without any issues:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotnet --version
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;It is unnecessary to reboot your machine, but as  &lt;a href="https://amelspahic.com/block-ads-tracking-and-telemetry-with-pi-hole-on-raspberry-pi-ubuntu-server-20-04" rel="noopener noreferrer"&gt;I use Raspberry Pi as a DHCP server&lt;/a&gt;, I noticed some problems obtaining the IP address, but reboot solves it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Since we installed .NET 6 SDK manually (using the script), we need to ensure that we have the required dependencies for the .NET by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;libc6 libgcc1 libgssapi-krb5-2 libicu66 libssl1.1 libstdc++6 zlib1g
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our machine is now ready to build and run .NET 6 applications, but this is still not the end. We need to let GitHub Actions know where to deploy, build, and run the code. Back to the GitHub repository.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2 - Create a self-hosted runner
&lt;/h2&gt;

&lt;p&gt;You can add self-hosted runners to a single repository. To add a self-hosted runner to a user repository, you must be the repository owner.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Navigate to the main page of the repository&lt;/li&gt;
&lt;li&gt;Click &lt;code&gt;Settings&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Select &lt;code&gt;Actions&lt;/code&gt; from the left sidebar&lt;/li&gt;
&lt;li&gt;Select &lt;code&gt;Runners&lt;/code&gt; submenu&lt;/li&gt;
&lt;li&gt;Click on &lt;code&gt;New self-hosted runner.&lt;/code&gt;
&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1640652932666%2FTNCVULlQa.png" alt="image.png"&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the next step, you need to choose what type of operating system and CPU architecture. If you are using Raspberry Pi 3 and higher versions, you probably have ARM64, but anyways, you can check that quickly by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;uname -m
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you see something like &lt;code&gt;aarch64&lt;/code&gt;, it means an ARM64 CPU. If you are on VPS, it will probably show something like &lt;code&gt;x86_64&lt;/code&gt;, which means your machine is running on Intel's 64-bit CPU. GitHub will provide you with the information needed to install the active-runner on your device. Follow it to the tee, but you can skip running the script with &lt;code&gt;./run.sh&lt;/code&gt; and &lt;code&gt;Using your self-hosted runner&lt;/code&gt; part because we will cover it later.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1640891111105%2F3f1J20akE.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1640891111105%2F3f1J20akE.png" alt="image.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The final screen should look like the image below. If you get, unexpected errors like &lt;code&gt;An error occurred while sending the request&lt;/code&gt; or &lt;code&gt;Resource temporarily unavailable&lt;/code&gt;, retry the command that caused it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1640653535773%2F2e8H0cOU8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1640653535773%2F2e8H0cOU8.png" alt="image.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Run a self-hosted runner as a background service
&lt;/h3&gt;

&lt;p&gt;In the previous configuration, we said to skip running &lt;code&gt;./run.sh&lt;/code&gt; because we want it to be a background process. Maybe someone likes it or uses it because of some security considerations, and it's OK, but the background process is my preferred way.&lt;/p&gt;

&lt;p&gt;Action-runners come with an option for installing runners as a service in the background. It will enable it by default, so the next time your machine restarts, the service will be up and running.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo&lt;/span&gt; ./svc.sh &lt;span class="nb"&gt;install
sudo&lt;/span&gt; ./svc.sh start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3 - Setup the application directory
&lt;/h2&gt;

&lt;p&gt;We created the action runner that will listen for the changes from GitHub Actions. Now we need to create a directory that will hold the application files. You can set it anywhere available, but I like to keep everything in &lt;code&gt;/var/www/&amp;lt;name_of_the_app&amp;gt;&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo mkdir&lt;/span&gt; /var/www/sample-app
&lt;span class="c"&gt;# I will give the directory ownership to the user `ubuntu` and its group&lt;/span&gt;
&lt;span class="nb"&gt;sudo chown &lt;/span&gt;ubuntu:ubuntu /var/www/sample-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we have a directory where we can output applications build assets. &lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4 - Create a GitHub Actions workflow
&lt;/h2&gt;

&lt;p&gt;We created a &lt;code&gt;self-hosted&lt;/code&gt; runner on our machine; now, we need to create a GitHub Action workflow to perform our deployment.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to your GitHub repository &lt;/li&gt;
&lt;li&gt;Select the &lt;code&gt;Actions&lt;/code&gt; tab&lt;/li&gt;
&lt;li&gt;Click on &lt;code&gt;set up a workflow yourself.&lt;/code&gt;
&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1640706454024%2Fs7_H1WYd6.png" alt="image.png"&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;GitHub will create a prepopulated file with a default filename which you can change. Replace the content of the file with the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CI&lt;/span&gt;

&lt;span class="c1"&gt;# Controls when the workflow will run&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Triggers the workflow on push request event for the master branch&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;    
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;master&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Our previously created self-hosted runner&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;self-hosted&lt;/span&gt;

    &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;matrix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;dotnet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;6.0.x"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

    &lt;span class="c1"&gt;# A sequence of tasks that will execute as part of the job&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# Checks out repository so our job can access it&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup .NET Core SDK ${{ matrix.dotnet-version }}&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-dotnet@v1.7.2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;dotnet-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ matrix.dotnet-version }}&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install dependencies&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dotnet restore&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dotnet build --configuration Release --no-restore&lt;/span&gt;

      &lt;span class="c1"&gt;# We will output publish files to the folder we previously created&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Publish&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dotnet publish -c Release -o /var/www/sample-app&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once we commit the file, GitHub will place it in the &lt;code&gt;.github/workflows/&lt;/code&gt; folder in the root of our repository, and the workflow process will start right away (if we did everything correctly). You can see the output of the workflow in the &lt;code&gt;Actions&lt;/code&gt; tab:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1640732165453%2FiWW41HP0Y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1640732165453%2FiWW41HP0Y.png" alt="image.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 5 - Create a background service for the application
&lt;/h2&gt;

&lt;p&gt;You probably already figured out that we don't have the &lt;code&gt;dotnet xxxx.dll&lt;/code&gt; command in our GitHub Actions workflow YAML file. Of course, there is a reason. When we start the application using the &lt;code&gt;dotnet&lt;/code&gt; command, it is triggered only as a foreground process, and in our case, the GitHub Actions workflow will wait as long as the application is running and won't complete.&lt;/p&gt;

&lt;p&gt;We could use something like &lt;code&gt;nohup&lt;/code&gt;, but it would create a new process every time we run the application (not to mention that we didn't specify the port on which it should run). Also, if our machine reboots, our application will be down until we start it manually.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# It may look cool, but we don't want this&lt;/span&gt;
&lt;span class="nb"&gt;nohup &lt;/span&gt;dotnet SampleApp.dll &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null 2&amp;gt;&amp;amp;1 &amp;amp;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While there might sometimes be better tools for the job, always consider using existing software first, and writing our service can give us a level of flexibility. &lt;/p&gt;

&lt;p&gt;To solve the issue, we will create a Linux user service that will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run our application after reboot&lt;/li&gt;
&lt;li&gt;Automatically restart the application if something internally happens and the application fails&lt;/li&gt;
&lt;li&gt;Ability to restart the application from a deployment script (GitHub Actions)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We create services in the &lt;code&gt;/etc/systemd/system&lt;/code&gt; directory, but we would need &lt;code&gt;elevated privileges&lt;/code&gt; to control them by default. Sure, there are ways to run &lt;code&gt;systemd&lt;/code&gt; services without using &lt;code&gt;sudo&lt;/code&gt; by tampering with &lt;code&gt;sudoers&lt;/code&gt; files or configuring &lt;code&gt;PolKit&lt;/code&gt;, but we will approach it differently. In the end, we want to give our GitHub Actions user the ability to run and restart the service without using &lt;code&gt;sudo&lt;/code&gt; or &lt;code&gt;PolKit&lt;/code&gt; configurations.&lt;/p&gt;

&lt;p&gt;We must locate user services in the &lt;code&gt;~/.config/systemd/user/&lt;/code&gt; directory. Let's create it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Using -p argument to create the whole path if it does not exist
mkdir -p ~/.config/systemd/user/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, you need to enable &lt;code&gt;lingering&lt;/code&gt; for the user on which we installed &lt;code&gt;GitHub Actions runner&lt;/code&gt;. In our case, that is the &lt;code&gt;ubuntu&lt;/code&gt; user, and it is necessary because it allows users who are not logged in to run long-running services. Otherwise, the user services will start on &lt;code&gt;user login&lt;/code&gt; instead of &lt;code&gt;boot&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;loginctl enable-linger ubuntu
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we need to create a &lt;code&gt;systemd&lt;/code&gt; unit file for the user (the service). We can name it however we want, but I prefer logical names, such as &lt;code&gt;name-of-the-application.service&lt;/code&gt;. Place it under the user's systemd instance.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nano ~/.config/systemd/user/sample-app.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are a couple of crucial things for which we prepared in previous steps, and we need to make sure we configure them correctly. The final configuration of &lt;code&gt;sample-app.service&lt;/code&gt; should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Unit]
Description=Example .NET 6 Application

[Service]
# This is the directory where our published files are
WorkingDirectory=/var/www/sample-app
# We set up `dotnet` PATH in Step 1. The second one is path of our executable
ExecStart=/usr/bin/dotnet /var/www/sample-app/SampleApp.dll --urls "http://0.0.0.0:5000"
Restart=always
# Restart service after 10 seconds if the dotnet service crashes
RestartSec=10
KillSignal=SIGINT
SyslogIdentifier=sample-app-log
# We can even set environment variables
Environment=ASPNETCORE_ENVIRONMENT=Production
Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false

[Install]
# When a systemd user instance starts, it brings up the per user target default.target
WantedBy=default.target
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We set &lt;code&gt;--urls "http://0.0.0.0:5000"&lt;/code&gt; in &lt;code&gt;ExecStart&lt;/code&gt; for &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/servers/kestrel?view=aspnetcore-6.0" rel="noopener noreferrer"&gt;Kestrel&lt;/a&gt; to listen on a specific port to test our application. I usually don't use Kestrel as an edge web server, but in a reverse proxy configuration with &lt;a href="https://www.nginx.com/" rel="noopener noreferrer"&gt;Nginx&lt;/a&gt;. For this tutorial, we will use Kestrel directly for brevity.&lt;/p&gt;

&lt;p&gt;If you are interested in the reverse proxy configuration, &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/proxy-load-balancer?view=aspnetcore-6.0" rel="noopener noreferrer"&gt;the following link&lt;/a&gt; describes it in great detail.&lt;/p&gt;

&lt;p&gt;After we are sure that we have the correct configuration, we can save the file with &lt;code&gt;Ctrl+X, Y&lt;/code&gt;. Let's reload &lt;code&gt;systemd&lt;/code&gt; and enable our service to run even after the reboot.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Reload `systemd`&lt;/span&gt;
systemctl &lt;span class="nt"&gt;--user&lt;/span&gt; daemon-reload

&lt;span class="c"&gt;# Enable the service at the next boot&lt;/span&gt;
systemctl &lt;span class="nt"&gt;--user&lt;/span&gt; &lt;span class="nb"&gt;enable &lt;/span&gt;sample-app.service

&lt;span class="c"&gt;# Start it right away&lt;/span&gt;
systemctl &lt;span class="nt"&gt;--user&lt;/span&gt; start sample-app.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is pretty easy to create a service on Linux, but it does not mean that it is the best solution. This tutorial didn't cover security considerations regarding the usage of &lt;code&gt;systemd&lt;/code&gt; services.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6 - Update the GitHub Actions workflow file
&lt;/h2&gt;

&lt;p&gt;Once we have our application service ready, we can update the GitHub Actions configuration to include our newly created service to run our application every time we push new code to the deployment branch.&lt;/p&gt;

&lt;p&gt;Edit the GitHub Actions workflow file created in Step 4 under the &lt;code&gt;&amp;lt;repository&amp;gt;/.github/workflows/&lt;/code&gt; folder. As the last step of our workflow, we will add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Restart the app&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;export XDG_RUNTIME_DIR=/run/user/$(id -u)&lt;/span&gt;
          &lt;span class="s"&gt;systemctl --user restart sample-app.service&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our final workflow YAML file should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CI&lt;/span&gt;

&lt;span class="c1"&gt;# Controls when the workflow will run&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Triggers the workflow on push request event for the master branch&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;    
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;master&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Our previously created self-hosted runner&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;self-hosted&lt;/span&gt;

    &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;matrix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;dotnet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;6.0.x"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

    &lt;span class="c1"&gt;# A sequence of tasks that will execute as part of the job&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# Checks out repository so our job can access it&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup .NET Core SDK ${{ matrix.dotnet-version }}&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-dotnet@v1.7.2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;dotnet-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ matrix.dotnet-version }}&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install dependencies&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dotnet restore&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dotnet build --configuration Release --no-restore&lt;/span&gt;

      &lt;span class="c1"&gt;# We will output publish files to the folder we previously created&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Publish&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dotnet publish -c Release -o /var/www/sample-app&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Restart the app&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;export XDG_RUNTIME_DIR=/run/user/$(id -u)&lt;/span&gt;
          &lt;span class="s"&gt;systemctl --user restart sample-app.service&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you deploy the task, you can track its status in Actions tab on GitHub repository. Now you can access your web application over the assigned URL and port.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final words
&lt;/h2&gt;

&lt;p&gt;I hope this text will help you complete that 2% of "what’s left of the project" and make deployment easier. If you find anything unclear, please comment here, and we will try to solve it if possible. Thank you for reading!&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>devops</category>
      <category>github</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Bring Your Home Network Anywhere For Free - Home VPN with WireGuard on Raspberry Pi + Pi-hole (Ubuntu Server 20.04 LTS)</title>
      <dc:creator>Amel Spahić</dc:creator>
      <pubDate>Fri, 31 Dec 2021 12:32:40 +0000</pubDate>
      <link>https://dev.to/amelspahic/bring-your-home-network-anywhere-for-free-home-vpn-with-wireguard-on-raspberry-pi-pi-hole-ubuntu-server-2004-lts-253a</link>
      <guid>https://dev.to/amelspahic/bring-your-home-network-anywhere-for-free-home-vpn-with-wireguard-on-raspberry-pi-pi-hole-ubuntu-server-2004-lts-253a</guid>
      <description>&lt;p&gt;In the &lt;a href="https://amelspahic.com/block-ads-tracking-and-telemetry-with-pi-hole-on-raspberry-pi-ubuntu-server-20-04" rel="noopener noreferrer"&gt;previous blog post&lt;/a&gt;, I talked about setting up Ubuntu Server 20.04 LTS and Pi-hole DNS on Raspberry Pi. You can go through the process step by step following &lt;a href="https://amelspahic.com/block-ads-tracking-and-telemetry-with-pi-hole-on-raspberry-pi-ubuntu-server-20-04" rel="noopener noreferrer"&gt;Block Ads, Tracking, and Telemetry With Pi-hole on Raspberry Pi (Ubuntu Server 20.04 LTS)&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Having Pi-hole set up on our home network, we will have a much better internet browsing experience without ads and better control of available resources (if any). Also, maybe you have a network-attached storage (NAS) in your network and would want to have access from anywhere, or you just want a safe browsing experience when connected to public WiFis.&lt;/p&gt;

&lt;p&gt;The setup above is limited only to your home network, and after a couple of days of browsing, you will think - why can't I bring this network setup wherever we go!? Well, &lt;strong&gt;YOU CAN&lt;/strong&gt;. A logical presumption would be to have a way to connect to our home network from anywhere and browse through it. Even when you connect from the other side of the world.&lt;/p&gt;

&lt;h2&gt;
  
  
  Virtual Private Network
&lt;/h2&gt;

&lt;p&gt;Virtual Private Network (VPN) allows us to connect our devices to another network over the internet in a secure manner. We can browse the internet using other computers' (server) internet connection. &lt;/p&gt;

&lt;p&gt;I am sure you came across internet ads for paid services like ExpressVPN, NordVPN, Surfshark, etc. They are awesome without a doubt, you can &lt;em&gt;fake&lt;/em&gt; your device's IP location and use some geographically limited services like Netflix, but it won't get you to your home network. And you have to pay for it. All VPNs use VPN protocols to create and secure your connection, so why shouldn't you, for your needs?&lt;/p&gt;

&lt;h2&gt;
  
  
  WireGuard or OpenVPN
&lt;/h2&gt;

&lt;p&gt;Two most popular VPN protocols used today are &lt;a href="https://www.wireguard.com/" rel="noopener noreferrer"&gt;WireGuard&lt;/a&gt; and &lt;a href="https://openvpn.net/" rel="noopener noreferrer"&gt;OpenVPN&lt;/a&gt;. There is no specific reason why I choose one over the other, but it is said that WireGuard is much faster than OpenVPN and it consumes around 15% less data, handles network changes better and appears to be just as secure (I don't know who said it).&lt;/p&gt;

&lt;h2&gt;
  
  
  WireGuard (or OpenVPN) on Raspberry Pi
&lt;/h2&gt;

&lt;p&gt;We could go through the manual installation instructions for WireGuard, but there is a great tool, &lt;a href="https://github.com/pivpn/pivpn" rel="noopener noreferrer"&gt;PiVPN &lt;/a&gt; which allows us to install the desired VPN very easily.&lt;/p&gt;

&lt;p&gt;Log in to your Raspberry Pi directly or via Secure Shell (SSH), and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -L https://install.pivpn.io | bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The process will use &lt;strong&gt;sudo&lt;/strong&gt; and install the necessary dependencies. Just wait for it to do its job. After installing the necessary packages, you will be prompted with graphical options:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1640544813377%2FBeZ1P9P-7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1640544813377%2FBeZ1P9P-7.png" alt="image.png"&gt;&lt;/a&gt;&lt;br&gt;
We previously talked about &lt;a href="https://amelspahic.com/block-ads-tracking-and-telemetry-with-pi-hole-on-raspberry-pi-ubuntu-server-20-04" rel="noopener noreferrer"&gt;setting up a static IP address on Ubuntu Server 20.04&lt;/a&gt;. PiVPN won't configure static IP for us because we are not using Raspbian OS for our Raspberry Pi.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1640544999740%2Fmwf8nWTaL.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1640544999740%2Fmwf8nWTaL.png" alt="image.png"&gt;&lt;/a&gt;&lt;br&gt;
Just accept default options, and be sure to select the WireGuard option when prompted.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1640545030423%2F9bDTuh2FVH.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1640545030423%2F9bDTuh2FVH.png" alt="image.png"&gt;&lt;/a&gt;&lt;br&gt;
You can change the default WireGuard port if necessary but have in mind that you will need it later, so make sure you remember it (I will use the default option, port 51820)&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1640545134762%2FBnjHc3f1y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1640545134762%2FBnjHc3f1y.png" alt="image.png"&gt;&lt;/a&gt;&lt;br&gt;
If you have a Pi-hole installation, PiVPN will detect it and ask if you want to use it as a DNS.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1640545215642%2F8a7H6sluS.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1640545215642%2F8a7H6sluS.png" alt="image.png"&gt;&lt;/a&gt;&lt;br&gt;
In the next steps, you will be prompted to use Public IP or DNS. Choose your public IP address.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1640545407415%2Fu1ocvG3gb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1640545407415%2Fu1ocvG3gb.png" alt="image.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If your ISP provides you with a dynamic IP address, there is a solution in the next post. For now, continue with this article.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Continue with the process and accept unattended upgrades to the server.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1640545588594%2FO8aP2NUO9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1640545588594%2FO8aP2NUO9.png" alt="image.png"&gt;&lt;/a&gt;&lt;br&gt;
Just follow the process and accept to reboot Raspberry Pi after the installation, so everything is set up. &lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1640545646885%2F0JehEKWts.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1640545646885%2F0JehEKWts.png" alt="image.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you use Pi-hole as a DHCP server, you won't have an internet connection while Raspberry Pi is rebooting. &lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  Port Forwarding
&lt;/h3&gt;

&lt;p&gt;To be able to connect to your Raspberry Pi VPN server, we need to set up a port forwarding option on your router. I have Technicolor CGA2121, but you can find that on every router, under settings (or advanced settings, usually under the &lt;strong&gt;Application &amp;amp; Gaming&lt;/strong&gt; option).&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1640546167647%2FyX2S9HPvw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1640546167647%2FyX2S9HPvw.png" alt="image.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Adding VPN Client
&lt;/h3&gt;

&lt;p&gt;To add a new VPN client user, use the integrated PiVPN command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pivpn add
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Choose your client name and hit ENTER.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You may have a warning to Run 'systemctl daemon-reload' to reload units, so just do it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now your client is ready to connect. You can find installation files  &lt;a href="https://www.wireguard.com/install/" rel="noopener noreferrer"&gt;here&lt;/a&gt; for different operating systems. &lt;/p&gt;

&lt;p&gt;For Android and iOS devices, there is a WireGuard application on PlayStore/AppStore, so download it. To quickly set up WireGuard VPN, from your Raspberry Pi run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pivpn -qr
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will have a QR code on the screen which you can read from your mobile phone to set it up.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1640547410681%2FhQKMEcyx6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1640547410681%2FhQKMEcyx6.png" alt="image.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now when you leave your home network, you are always a &lt;em&gt;flip of the switch&lt;/em&gt; away from it.&lt;/p&gt;
&lt;h2&gt;
  
  
  Pi-hole DNS Troubleshooting
&lt;/h2&gt;

&lt;p&gt;If you installed PiVPN before Pi-hole, edit the PiVPN configuration with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ sudo nano /etc/pivpn/wireguard/setupVars.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Remove the &lt;code&gt;pivpnDNS1=[...]&lt;/code&gt; and &lt;code&gt;pivpnDNS2=[...]&lt;/code&gt; lines&lt;/li&gt;
&lt;li&gt;Add this line &lt;code&gt;pivpnDNS1=192.168.0.50&lt;/code&gt; (your Pi-hole IP might be different) to point clients to the Pi-hole IP&lt;/li&gt;
&lt;li&gt;Save the file with &lt;code&gt;Ctrl+X, Y&lt;/code&gt; and exit&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;pihole -a -i local&lt;/code&gt; to tell Pi-hole to listen on all interfaces&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Dynamic IP Address
&lt;/h2&gt;

&lt;p&gt;If you are lucky enough or you are not sorry to pay for the static IP address, you can skip this part. Otherwise, &lt;a href="https://amelspahic.com/set-up-dynamic-dns-for-dynamic-ip-addresses-at-home" rel="noopener noreferrer"&gt;here&lt;/a&gt; you can read how to Set up Dynamic DNS for Dynamic IP Addresses at Home for free. &lt;/p&gt;

&lt;h2&gt;
  
  
  Final Words
&lt;/h2&gt;

&lt;p&gt;I hope this tutorial will help you set up your VPN communication and bring even more privacy, security, and comfort while browsing the internet.&lt;/p&gt;

</description>
      <category>privacy</category>
      <category>tutorial</category>
      <category>linux</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Block Ads, Tracking, and Telemetry With Pi-hole on Raspberry Pi (Ubuntu Server 20.04 LTS)</title>
      <dc:creator>Amel Spahić</dc:creator>
      <pubDate>Fri, 31 Dec 2021 12:15:59 +0000</pubDate>
      <link>https://dev.to/amelspahic/block-ads-tracking-and-telemetry-with-pi-hole-on-raspberry-pi-ubuntu-server-2004-lts-17ga</link>
      <guid>https://dev.to/amelspahic/block-ads-tracking-and-telemetry-with-pi-hole-on-raspberry-pi-ubuntu-server-2004-lts-17ga</guid>
      <description>&lt;p&gt;While internet advertising is a significant source of revenue for your favorite websites, some individuals want to prevent it for a variety of reasons, including performance or privacy concerns. You could install blocking software on each of your devices, but the most efficient method is to use Pi-hole to establish a server that filters all of your web traffic at the local network level. #pihole&lt;/p&gt;

&lt;p&gt;The best explanation on &lt;strong&gt;what is Pi-hole&lt;/strong&gt; you can find on the  &lt;a href="https://docs.pi-hole.net/" rel="noopener noreferrer"&gt;Pi-hole® official website&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The Pi-hole® is a DNS sinkhole that protects your devices from unwanted content, without installing any client-side software.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Easy-to-install: our versatile installer walks you through the process and takes less than ten minutes&lt;/li&gt;
&lt;li&gt;Resolute: content is blocked in non-browser locations, such as ad-laden mobile apps and smart TVs&lt;/li&gt;
&lt;li&gt;Responsive: seamlessly speeds up the feel of everyday browsing by caching DNS queries&lt;/li&gt;
&lt;li&gt;Lightweight: runs smoothly with minimal hardware and software requirements&lt;/li&gt;
&lt;li&gt;Robust: a command-line interface that is quality assured for interoperability&lt;/li&gt;
&lt;li&gt;Insightful: a beautiful responsive Web Interface dashboard to view and control your Pi-hole&lt;/li&gt;
&lt;li&gt;Versatile: can optionally function as a DHCP server, ensuring all your devices are protected automatically&lt;/li&gt;
&lt;li&gt;Scalable: capable of handling hundreds of millions of queries when installed on server-grade hardware&lt;/li&gt;
&lt;li&gt;Modern: blocks ads over both IPv4 and IPv6&lt;/li&gt;
&lt;li&gt;Free: open-source software which helps ensure you are the sole person in control of your privacy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This post should be as simple as possible and for everyone with a couple of bucks for the &lt;a href="https://www.raspberrypi.org/" rel="noopener noreferrer"&gt;Raspberry Pi&lt;/a&gt; with an ethernet capability. An even better idea for those with a dust-collecting RPi somewhere in the room.&lt;/p&gt;

&lt;p&gt;This post will use Raspberry Pi 4 with 4GB of RAM as an example, but it really does not matter since Pi-hole as long as you have a device with at least 512 MB of RAM and 2 GB of space.&lt;/p&gt;

&lt;h2&gt;
  
  
  Requirements
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;An internet connection&lt;/li&gt;
&lt;li&gt;A computer with a microSD card reader&lt;/li&gt;
&lt;li&gt;Raspberry Pi with ethernet capabilities&lt;/li&gt;
&lt;li&gt;A microSD card (at least 8GB, 16GB is recommended) &lt;/li&gt;
&lt;li&gt;A monitor with HDMI&lt;/li&gt;
&lt;li&gt;A micro HDMI cable (if you have Raspberry Pi 4)&lt;/li&gt;
&lt;li&gt;A USB keyboard&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Process
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Installing Operating System
&lt;/h3&gt;

&lt;p&gt;We need to set up an operating system (OS) on our microSD card which will be used as persistence on Raspberry Pi. I will be using &lt;strong&gt;Ubuntu Server 20.04 LTS x64&lt;/strong&gt; for that purpose, but you can use any other Ubuntu version or Raspberry Pi OS.&lt;/p&gt;

&lt;p&gt;The easiest way to format and install the required OS on a microSD card is by using the existing tool which can be downloaded from &lt;a href="https://www.raspberrypi.com/software/" rel="noopener noreferrer"&gt;Raspberry Pi Imager&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Insert the microSD card into your computer, and start already installed Raspberry Pi Imager.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1640430959196%2FhwcF7jcZR.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1640430959196%2FhwcF7jcZR.png" alt="2021-12-25_11h53_34.png"&gt;&lt;/a&gt;&lt;br&gt;
From the initial screen, select &lt;strong&gt;CHOOSE OS&lt;/strong&gt; and get your preferred operating system. Ubuntu can be found under the &lt;strong&gt;Other general-purpose OS&lt;/strong&gt; group.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1640430444541%2FzH1FYL55k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1640430444541%2FzH1FYL55k.png" alt="image.png"&gt;&lt;/a&gt;&lt;br&gt;
Select the image, open the &lt;strong&gt;SD Card&lt;/strong&gt; menu, and select the inserted microSD card. Click &lt;strong&gt;WRITE&lt;/strong&gt; and wait a couple of minutes for the operating system to be downloaded, installed, and verified on microSD. Once the process is complete, you will be notified that you can safely remove the microSD card from the slot.&lt;/p&gt;
&lt;h3&gt;
  
  
  Booting Up
&lt;/h3&gt;

&lt;p&gt;Make sure your Raspberry Pi is &lt;strong&gt;off&lt;/strong&gt; before you insert the microSD card into the dedicated slot. Connect the ethernet cable to the router, keyboard, monitor, and finally Raspberry Pi power supply. Wait a bit and you will see the operating system booting up.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1640432500809%2FNvmjQzjzv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1640432500809%2FNvmjQzjzv.png" alt="image.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Ubuntu Server does not have graphical installation, but the process is pretty straightforward.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once the system is booted up, you will see a login prompt, but don't do anything for now. Wait for a couple of minutes for everything to set up and then perform the login. The default username and password for Ubuntu Server 20.04 LTS  is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;username: ubuntu
password: ubuntu
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the successful login, it will ask for a new password, so be sure to create a secure password and remember it. That is your &lt;code&gt;sudo&lt;/code&gt; password which grants you administrative privileges, and it will be used frequently.&lt;/p&gt;

&lt;h4&gt;
  
  
  Update the OS
&lt;/h4&gt;

&lt;p&gt;I know we did the clean install of the operating system, but it does not mean that the Raspberry Pi Imager has always up-to-date repositories. To update your system and packages with the latest patches, just run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo apt update

sudo apt upgrade
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the Linux kernel is updated, you will need to reboot the system. You can do it with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo reboot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Static IP
&lt;/h4&gt;

&lt;p&gt;While we are at it, we should set up the static IP address for our device right from the start. It will be necessary for the Pi-hole later.&lt;/p&gt;

&lt;p&gt;Ubuntu Server 20.04 LTS uses Netplan for the network configuration and can be found in &lt;code&gt;/etc/netplan/&lt;/code&gt; directory. There should be a file already in one of these forms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;01-netcfg.yaml&lt;/li&gt;
&lt;li&gt;01-network-manager-all.yaml&lt;/li&gt;
&lt;li&gt;50-cloud-init.yaml&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Otherwise, if you cannot find the configuration file, you can try to generate a new one, executing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ sudo netplan generate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To configure a static IP address, you need to change the configuration in the file. This is how mine looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# This file is generated from information provided by the datasource.  Changes&lt;/span&gt;
&lt;span class="c1"&gt;# to it will not persist across an instance reboot.  To disable cloud-init's&lt;/span&gt;
&lt;span class="c1"&gt;# network configuration capabilities, write a file&lt;/span&gt;
&lt;span class="c1"&gt;# /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg with the following:&lt;/span&gt;
&lt;span class="c1"&gt;# network: {config: disabled}&lt;/span&gt;
&lt;span class="na"&gt;network&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;ethernets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;eth0&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;dhcp4&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;no&lt;/span&gt;
            &lt;span class="na"&gt;addresses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;192.168.0.50/24&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
            &lt;span class="na"&gt;gateway4&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;192.168.0.1&lt;/span&gt;
            &lt;span class="na"&gt;nameservers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;addresses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;8.8.8.8&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;&lt;span class="nv"&gt;8.8.4.4&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This configuration indicates that the static IP address will be &lt;strong&gt;192.168.0.50&lt;/strong&gt; and the gateway (routers') IP address is &lt;strong&gt;192.168.0.1&lt;/strong&gt;. Please check your router IP address so you can change values accordingly.&lt;/p&gt;

&lt;h4&gt;
  
  
  Remote Access
&lt;/h4&gt;

&lt;p&gt;I don't like having Raspberry Pi connected to anything except the network, so I prefer using Secure Shell (SSH) to operate with Raspberry Pi. If you have a device on the same network, it is pretty easy. Don't disconnect the peripherals yet, we need some basic information. By default, SSH on Ubuntu Server obtained from Raspberry Pi Imager repository has SSH enabled. We can check this by checking the status with the command &lt;code&gt;sudo systemctl status ssh&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ sudo systemctl status ssh
● ssh.service - OpenBSD Secure Shell server
     Loaded: loaded (/lib/systemd/system/ssh.service; enabled; vendor preset: enabled)
     Active: active (running) since Sat 2021-12-25 12:54:10 UTC; 12min ago
       Docs: man:sshd(8)
             man:sshd_config(5)
    Process: 1733 ExecStartPre=/usr/sbin/sshd -t (code=exited, status=0/SUCCESS)
   Main PID: 1777 (sshd)
      Tasks: 1 (limit: 4435)
     CGroup: /system.slice/ssh.service
             └─1777 sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups

Dec 25 12:54:09 ubuntu systemd[1]: Starting OpenBSD Secure Shell server...
Dec 25 12:54:10 ubuntu sshd[1777]: Server listening on 0.0.0.0 port 22.
Dec 25 12:54:10 ubuntu sshd[1777]: Server listening on :: port 22.
Dec 25 12:54:10 ubuntu systemd[1]: Started OpenBSD Secure Shell server.
Dec 25 12:54:36 ubuntu sshd[1966]: Accepted password for ubuntu from 192.168.0.183 port 63829 ssh2
Dec 25 12:54:36 ubuntu sshd[1966]: pam_unix(sshd:session): session opened for user ubuntu by (uid=0)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If our SSH has the status &lt;code&gt;Active: active (running)&lt;/code&gt;, we are ready to continue. We need the IP address of the Raspberry Pi which we can obtain by running the next command (it should be a static IP address that we specified earlier):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ hostname -I
192.168.0.50
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since we already know our username is &lt;strong&gt;ubuntu&lt;/strong&gt;, we can now safely disconnect everything from the Raspberry Pi except the ethernet cable.&lt;/p&gt;

&lt;p&gt;I use PowerShell to connect from my Windows 11 machine, but you can use whatever command-line program you prefer.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Don't forget you need to be on the same network&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To log in remotely, use previously obtained information about your hostname and IP address. The command format is &lt;code&gt;ssh hostname@IPaddress&lt;/code&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1640438305156%2FhXfyDRHrH.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1640438305156%2FhXfyDRHrH.png" alt="image.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you like this PowerShell look, there is a great blog post from @&lt;a href="https://dev.to@shanselman"&gt;Scott Hanselman&lt;/a&gt;  &lt;a href="https://www.hanselman.com/blog/my-ultimate-powershell-prompt-with-oh-my-posh-and-the-windows-terminal" rel="noopener noreferrer"&gt;HERE&lt;/a&gt; &lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;
  
  
  Unable to resolve host
&lt;/h4&gt;

&lt;p&gt;If you get the following error&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo: unable to resolve host ubuntu: No address associated with hostname
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It means that your hostname is not configured for the IP address. To solve it, add the hostname in &lt;code&gt;/etc/hosts&lt;/code&gt; file. To find the hostname, you can check it in the &lt;code&gt;/etc/hostname&lt;/code&gt; file using &lt;code&gt;cat&lt;/code&gt; command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ cat /etc/hostname
ubuntu # &amp;lt;-- this is your hostname
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can edit your &lt;code&gt;hosts&lt;/code&gt; file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ sudo nano /etc/hosts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add your hostname, obtained with the previous command, to the row where localhost is defined:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;127.0.0.1 localhost heregoesyourhostname # &amp;lt;- instead of this

# The following lines are desirable for IPv6 capable hosts
::1 ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
ff02::3 ip6-allhosts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pi-hole Installation
&lt;/h3&gt;

&lt;p&gt;Installation of Pi-hole is pretty straightforward, and you can do it from your computer if you obtained information for the SSH connection.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ curl -sSL https://install.pi-hole.net | bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will be prompted a couple of times to select configuration:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1640441083811%2F_GTgt8L2g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1640441083811%2F_GTgt8L2g.png" alt="image.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every screen contains information about the configuration installation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1640447387720%2FC2U6UN2CT.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1640447387720%2FC2U6UN2CT.png" alt="image.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We already set up a static IP address, so we can just continue with the process. For the rest of the process, you can choose default options (including Web admin interface). Once we complete all the steps, we will have the final screen with the Web interface password.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1640447565034%2Ff80kO-MXB.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1640447565034%2Ff80kO-MXB.png" alt="image.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can access to Web admin interface by navigating to the IP address we specified through the browser&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#&amp;lt;predefined static IP addres&amp;gt;/admin&lt;/span&gt;
192.168.0.50/admin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Change Password
&lt;/h4&gt;

&lt;p&gt;Change your Pi-hole Web interface password using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ sudo pihole -a -p
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Router Configuration
&lt;/h3&gt;

&lt;p&gt;Once we have everything installed, it is necessary to tell our router to use Pi-hole as DNS. For this process, we need to have access to the network router (to which Raspberry Pi is connected to). A lot of ISPs allow this kind of configuration, but unfortunately, mine doesn't. &lt;/p&gt;

&lt;p&gt;I have Technicolor CGA2121 DOCSIS 3.0 Wireless Gateway, and no option to manually change upstream DNS for DHCP connection (I don't have a static IP from my ISP). Thankfully, there is a way to override that behavior using Pi-holes' integrated DHCP server. For that purpose, we need to disable the DHCP server on the router and turn on DHCP on Pi-hole.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1640448530285%2FMVb6DMy9q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1640448530285%2FMVb6DMy9q.png" alt="image.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To disable the routers' DHCP, we need to log in and disable it manually.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1640448596669%2Ftv_wt_5KO.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1640448596669%2Ftv_wt_5KO.png" alt="image.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Blocklists
&lt;/h3&gt;

&lt;p&gt;Pi-hole comes with an optional blocklist that you could select during the installation process, and it is usually sufficient for most people.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This blocklist is well-maintained and provides effective blocking without interfering with routine operations. While this may be sufficient for many, users frequently discover that they want to add their own custom lists to improve blocking capabilities. &lt;/p&gt;

&lt;h4&gt;
  
  
  Blocklist Collections
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://firebog.net/" rel="noopener noreferrer"&gt;The Firebog&lt;/a&gt;,  &lt;a href="https://github.com/lightswitch05/hosts" rel="noopener noreferrer"&gt;DeveloperDan&lt;/a&gt; and you can check Reddit or Google Search.&lt;/p&gt;

&lt;p&gt;The easiest way to update the blocklist is through the Web interface. From the blocklist websites above, select lists that you want to use (I suggest reading a bit about them, so you don't come across some unwanted behavior while browsing).&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1640451145607%2F1UcMWqxvk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1640451145607%2F1UcMWqxvk.png" alt="image.png"&gt;&lt;/a&gt;&lt;br&gt;
To update the blocklist on your Pi-hole, you need to paste selected lists into the &lt;strong&gt;Adlists group management&lt;/strong&gt; tab.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1640451594216%2F3uQuOWvpHD.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1640451594216%2F3uQuOWvpHD.png" alt="image.png"&gt;&lt;/a&gt;&lt;br&gt;
After that, we need to update the Pi-hole Gravity.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1640451549906%2F8bkvzfuXH.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1640451549906%2F8bkvzfuXH.png" alt="image.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Tip
&lt;/h2&gt;

&lt;p&gt;Strange things will happen while browsing, for sure, especially if you put a lot of blocklists. To check if that something is because of the Pi-hole configuration, there is an option to disable the Pi-hole for some time. &lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1640452606552%2FTOjmsLGo-.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1640452606552%2FTOjmsLGo-.png" alt="image.png"&gt;&lt;/a&gt;&lt;br&gt;
While the Pi-hole is disabled, you can try what you intended, and after the countdown, everything will go back as it was before.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final words
&lt;/h2&gt;

&lt;p&gt;I hope this post helped you set up the Pi-hole for a better internet browsing experience, and I'm sure this will be one of the best improvements you could make for your home or business network.&lt;/p&gt;

&lt;p&gt;Happy browsing without ads!&lt;/p&gt;

&lt;p&gt;P.S. Read the documentation, please.&lt;/p&gt;

</description>
      <category>ubuntu</category>
      <category>privacy</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
