<?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: Chaithanya Chowdhary</title>
    <description>The latest articles on DEV Community by Chaithanya Chowdhary (@chaithuchowdhary).</description>
    <link>https://dev.to/chaithuchowdhary</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%2F3687775%2F4f742f82-fb8c-4bb2-a531-f06fa54d92ab.jpeg</url>
      <title>DEV Community: Chaithanya Chowdhary</title>
      <link>https://dev.to/chaithuchowdhary</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/chaithuchowdhary"/>
    <language>en</language>
    <item>
      <title>The Zero-Trust Homelab Manual: MacVLAN, Private PKI, and Tailscale</title>
      <dc:creator>Chaithanya Chowdhary</dc:creator>
      <pubDate>Wed, 31 Dec 2025 17:37:18 +0000</pubDate>
      <link>https://dev.to/chaithuchowdhary/the-zero-trust-homelab-manual-macvlan-private-pki-and-tailscale-46gj</link>
      <guid>https://dev.to/chaithuchowdhary/the-zero-trust-homelab-manual-macvlan-private-pki-and-tailscale-46gj</guid>
      <description>&lt;p&gt;&lt;strong&gt;Author:&lt;/strong&gt; Chaithanya Chowdhary Enugu&lt;br&gt;
&lt;strong&gt;Date:&lt;/strong&gt; December 31 2025&lt;br&gt;
&lt;strong&gt;Level:&lt;/strong&gt; Advanced&lt;/p&gt;

&lt;p&gt;Most homelab guides take shortcuts. They use Docker Bridge networks (hiding everything behind one IP), HTTP-only connections, or rely on public domains.&lt;/p&gt;

&lt;p&gt;I wanted a &lt;strong&gt;Production-Grade&lt;/strong&gt; environment at home.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Real Networking:&lt;/strong&gt; Containers should have physical IPs on my network (MacVLAN).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real Security:&lt;/strong&gt; Zero ports open to the internet, but valid SSL certificates everywhere (Private PKI).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real Access:&lt;/strong&gt; Accessible from anywhere in the world without exposing the network (Tailscale).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the comprehensive documentation on how to build this exact stack on Ubuntu Server.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fq1zva6xz8s21s0j3bpax.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fq1zva6xz8s21s0j3bpax.png" alt=" " width="800" height="492"&gt;&lt;/a&gt;&lt;/p&gt;


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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hardware:&lt;/strong&gt; A dedicated server/VM (Ubuntu 22.04/24.04 LTS).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Network:&lt;/strong&gt; Access to your Router to set Static IPs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Domain:&lt;/strong&gt; A reserved local domain (e.g., &lt;code&gt;home.lan&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  &lt;strong&gt;1: The Network Foundation (MacVLAN)&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Standard Docker networks hide containers behind the host. We want our core infrastructure (Nginx Proxy Manager, Pi-hole) to have their own IP addresses on the physical network.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;1.1 Prepare the Host&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;First, ensure your Ubuntu host has a static IP. In this guide, we assume:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Host Server IP:&lt;/strong&gt; &lt;code&gt;192.168.1.137&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gateway/Router:&lt;/strong&gt; &lt;code&gt;192.168.1.1&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interface Name:&lt;/strong&gt; &lt;code&gt;enp1s0&lt;/code&gt; (Check yours with &lt;code&gt;ip a&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;1.2 Create the Docker MacVLAN Network&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;We will reserve a small slice of IPs (e.g., &lt;code&gt;.138&lt;/code&gt; and &lt;code&gt;.139&lt;/code&gt;) strictly for Docker to avoid IP conflicts with other devices.&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;# Create the network attached to your physical interface&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;docker network create &lt;span class="nt"&gt;-d&lt;/span&gt; macvlan &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--subnet&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.1.0/24 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--gateway&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.1.1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--ip-range&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.1.138/31 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;parent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;enp1s0 &lt;span class="se"&gt;\&lt;/span&gt;
  npm_network
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;--ip-range&lt;/code&gt; strictly limits Docker to assigning only &lt;code&gt;.138&lt;/code&gt; and &lt;code&gt;.139&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;1.3 The "Host Shim" (The Critical Fix)&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;By security design, a MacVLAN container cannot talk to its own Host. To fix this, we must create a virtual bridge (Shim).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Create a persistent startup script:&lt;/strong&gt;&lt;br&gt;
Create a file at &lt;code&gt;/usr/local/bin/macvlan-shim.sh&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="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# Create the shim interface&lt;/span&gt;
ip &lt;span class="nb"&gt;link &lt;/span&gt;add npm-shim &lt;span class="nb"&gt;link &lt;/span&gt;enp1s0 &lt;span class="nb"&gt;type &lt;/span&gt;macvlan mode bridge
&lt;span class="c"&gt;# Assign an IP to the host-side of the shim (must be unique)&lt;/span&gt;
ip addr add 192.168.1.140/32 dev npm-shim
&lt;span class="c"&gt;# Bring it up&lt;/span&gt;
ip &lt;span class="nb"&gt;link set &lt;/span&gt;npm-shim up
&lt;span class="c"&gt;# Route traffic to the Docker MacVLAN range through the shim&lt;/span&gt;
ip route add 192.168.1.138/31 dev npm-shim
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Make it executable:&lt;/strong&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 chmod&lt;/span&gt; +x /usr/local/bin/macvlan-shim.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Create a Systemd Service to run it on boot:&lt;/strong&gt;&lt;br&gt;
Create &lt;code&gt;/etc/systemd/system/macvlan-shim.service&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[Unit]&lt;/span&gt;
&lt;span class="py"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;MacVLAN Shim for Docker Host Access&lt;/span&gt;
&lt;span class="py"&gt;After&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;network.target&lt;/span&gt;

&lt;span class="nn"&gt;[Service]&lt;/span&gt;
&lt;span class="py"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;oneshot&lt;/span&gt;
&lt;span class="py"&gt;ExecStart&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/usr/local/bin/macvlan-shim.sh&lt;/span&gt;

&lt;span class="nn"&gt;[Install]&lt;/span&gt;
&lt;span class="py"&gt;WantedBy&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;multi-user.target&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Enable the service:&lt;/strong&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 &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; macvlan-shim.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  &lt;strong&gt;2: The Identity Layer (Host-Native Step-CA)&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;We install the Certificate Authority directly on the OS (Bare Metal) for maximum stability.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;2.1 Installation&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Download and install the &lt;code&gt;.deb&lt;/code&gt; packages.&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://dl.smallstep.com/gh-release/certificates/gh-release-header/v0.29.0/step-ca_0.29.0-1_amd64.deb
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; ./step-ca_0.29.0-1_amd64.deb

wget https://dl.smallstep.com/gh-release/cli/gh-release-header/v0.29.0/step-cli_0.29.0-1_amd64.deb
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; ./step-cli_0.29.0-1_amd64.deb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;2.2 Initialization&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Initialize the CA to listen on port 9000.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;step ca init &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"HomeLab-CA"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
             &lt;span class="nt"&gt;--dns&lt;/span&gt; &lt;span class="s2"&gt;"ca.home.lan"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
             &lt;span class="nt"&gt;--address&lt;/span&gt; &lt;span class="s2"&gt;":9000"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
             &lt;span class="nt"&gt;--provisioner&lt;/span&gt; &lt;span class="s2"&gt;"admin@home.lan"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Note: Save the password generated here!&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;2.3 Create the Systemd Service&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;To keep the CA running 24/7.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Save the password securely:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"YOUR_PASSWORD_HERE"&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /etc/step-ca/password.txt
&lt;span class="nb"&gt;sudo chmod &lt;/span&gt;600 /etc/step-ca/password.txt
&lt;span class="nb"&gt;sudo chown &lt;/span&gt;step:step /etc/step-ca/password.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Create the Service File:&lt;/strong&gt; &lt;code&gt;/etc/systemd/system/step-ca.service&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[Unit]&lt;/span&gt;
&lt;span class="py"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;step-ca service&lt;/span&gt;
&lt;span class="py"&gt;After&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;network.target&lt;/span&gt;
&lt;span class="py"&gt;StartLimitIntervalSec&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;

&lt;span class="nn"&gt;[Service]&lt;/span&gt;
&lt;span class="py"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;simple&lt;/span&gt;
&lt;span class="py"&gt;User&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;step&lt;/span&gt;
&lt;span class="py"&gt;Group&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;step&lt;/span&gt;
&lt;span class="py"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;STEPPATH=/home/ubuntu/.step&lt;/span&gt;
&lt;span class="py"&gt;ExecStart&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/usr/bin/step-ca /home/ubuntu/.step/config/ca.json --password-file /etc/step-ca/password.txt&lt;/span&gt;
&lt;span class="py"&gt;Restart&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;always&lt;/span&gt;
&lt;span class="py"&gt;RestartSec&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;

&lt;span class="nn"&gt;[Install]&lt;/span&gt;
&lt;span class="py"&gt;WantedBy&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;multi-user.target&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Start the CA:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&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;systemctl daemon-reload
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; step-ca
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;2.4 Generate the Wildcard Certificate&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;This single certificate will secure all your services.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;step ca certificate &lt;span class="s2"&gt;"*.home.lan"&lt;/span&gt; wildcard.crt wildcard.key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;(Keep these files safe; we will upload them to Nginx Proxy Manager later.)&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;3: The Core Infrastructure (NPM &amp;amp; Pi-hole)&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;We use &lt;strong&gt;Docker Compose&lt;/strong&gt; to deploy the networking stack on the MacVLAN network.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;File:&lt;/strong&gt; &lt;code&gt;docker-compose.yml&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.8'&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;# --- Nginx Proxy Manager ---&lt;/span&gt;
  &lt;span class="na"&gt;npm&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="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;jc21/nginx-proxy-manager:latest'&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm&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;unless-stopped&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="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;80:80'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;81:81'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;443:443'&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;./npm-data:/data&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./letsencrypt:/etc/letsencrypt&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;npm_network&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;ipv4_address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;192.168.1.138&lt;/span&gt;

  &lt;span class="c1"&gt;# --- Pi-hole DNS ---&lt;/span&gt;
  &lt;span class="na"&gt;pihole&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;pihole/pihole:latest&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pihole&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;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;TZ&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;America/Chicago'&lt;/span&gt;
      &lt;span class="na"&gt;WEBPASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;securepassword'&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;./pihole/etc-pihole:/etc/pihole&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./pihole/etc-dnsmasq.d:/etc/dnsmasq.d&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;npm_network&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;ipv4_address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;192.168.1.139&lt;/span&gt;

&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;npm_network&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;external&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Run &lt;code&gt;docker-compose up -d&lt;/code&gt; to deploy.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;4: The Vault (Official Bitwarden)&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;For the password manager, we use the official install script. Because this script is aggressive, we let it run on the Host Network (binding to ports) rather than forcing it into MacVLAN.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Follow this guide to self-host bitwarden &lt;a href="https://bitwarden.com/help/install-on-premise-linux/" rel="noopener noreferrer"&gt;bitwarden.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;4.1 Install&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-Lso&lt;/span&gt; bitwarden.sh https://go.btwrdn.co/bw-sh &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;chmod &lt;/span&gt;700 bitwarden.sh
./bitwarden.sh &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Domain:&lt;/strong&gt; &lt;code&gt;bitwarden.home.lan&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SSL:&lt;/strong&gt; No (We handle SSL at Nginx Proxy Manager).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;4.2 Configure Ports&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Bitwarden defaults to 80/443, which conflicts with NPM. We must move it.&lt;br&gt;
Edit &lt;code&gt;./bwdata/config.yml&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;http_port: 8080&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;https_port:&lt;/code&gt; (Leave empty)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;4.3 Rebuild&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./bitwarden.sh rebuild
./bitwarden.sh start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Bitwarden is now running on &lt;code&gt;192.168.1.137:8080&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;5: Connecting the Dots&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;5.1 Pi-hole DNS Records&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Open Pi-hole (&lt;code&gt;http://192.168.1.139/admin&lt;/code&gt;). Go to &lt;strong&gt;Local DNS &amp;gt; DNS Records&lt;/strong&gt;.&lt;br&gt;
Map all your domains to the &lt;strong&gt;NPM IP (.138)&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;bitwarden.home.lan&lt;/code&gt; -&amp;gt; &lt;code&gt;192.168.1.138&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;portainer.home.lan&lt;/code&gt; -&amp;gt; &lt;code&gt;192.168.1.138&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;npm.home.lan&lt;/code&gt; -&amp;gt; &lt;code&gt;192.168.1.138&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;5.2 Nginx Proxy Manager Config&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Open NPM (&lt;code&gt;http://192.168.1.138:81&lt;/code&gt;).&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;SSL:&lt;/strong&gt; Go to SSL Certificates &amp;gt; Add Custom &amp;gt; Upload your &lt;code&gt;wildcard.crt&lt;/code&gt; and &lt;code&gt;wildcard.key&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Proxy Host (Bitwarden):&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Domain:&lt;/strong&gt; &lt;code&gt;bitwarden.home.lan&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Forward IP:&lt;/strong&gt; &lt;code&gt;192.168.1.137&lt;/code&gt; (The Host IP)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Forward Port:&lt;/strong&gt; &lt;code&gt;8080&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SSL:&lt;/strong&gt; Custom Wildcard (Force SSL ON).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Advanced:&lt;/strong&gt; Add this code to fix Identity errors:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://192.168.1.137:8080&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Host&lt;/span&gt; &lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Real-IP&lt;/span&gt; &lt;span class="nv"&gt;$remote_addr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Forwarded-For&lt;/span&gt; &lt;span class="nv"&gt;$proxy_add_x_forwarded_for&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Forwarded-Proto&lt;/span&gt; &lt;span class="nv"&gt;$scheme&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;h2&gt;
  
  
  &lt;strong&gt;6: Remote Access (Tailscale)&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;To access this private cloud from 5G/Coffee Shops:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Install Tailscale on Host:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://tailscale.com/install.sh | sh
&lt;span class="nb"&gt;sudo &lt;/span&gt;tailscale up &lt;span class="nt"&gt;--advertise-routes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.1.0/24
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Configure Console:&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Go to Tailscale Admin &amp;gt; DNS.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Global Nameservers:&lt;/strong&gt; Add your Pi-hole's Tailscale IP (100.x.x.x).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Override Local DNS:&lt;/strong&gt; &lt;strong&gt;Enable&lt;/strong&gt; (Crucial!).&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;7: The "Samsung/Android" Fix&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Android devices are stubborn. Even with the setup above, they will try to bypass your Pi-hole.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Disable Private DNS:&lt;/strong&gt; Android Settings &amp;gt; Connections &amp;gt; More Connection Settings &amp;gt; Private DNS &amp;gt; &lt;strong&gt;OFF&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Router Config:&lt;/strong&gt; Log into your physical router and &lt;strong&gt;Disable IPv6&lt;/strong&gt;. (Android prefers IPv6 DNS and will bypass your Pi-hole if this is on).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Static WiFi Settings:&lt;/strong&gt; On the phone, set the WiFi connection to Static and set &lt;strong&gt;DNS 1&lt;/strong&gt; and &lt;strong&gt;DNS 2&lt;/strong&gt; both to &lt;code&gt;192.168.1.139&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Conclusion&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;You now have a fully functional Private Cloud.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Locally:&lt;/strong&gt; Devices use Pi-hole to resolve &lt;code&gt;.lan&lt;/code&gt; domains to Nginx, which serves valid SSL.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Remotely:&lt;/strong&gt; Tailscale tunnels your DNS requests back home, providing the exact same experience as if you were sitting on your couch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security:&lt;/strong&gt; You own the keys, the data, and the network.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>homelab</category>
      <category>tailscale</category>
      <category>pihole</category>
      <category>nginx</category>
    </item>
  </channel>
</rss>
