{"id":3283,"date":"2026-05-29T07:01:32","date_gmt":"2026-05-29T07:01:32","guid":{"rendered":"https:\/\/muhammadsoliman.com\/?p=3283"},"modified":"2026-05-29T07:02:21","modified_gmt":"2026-05-29T07:02:21","slug":"build-traefik-on-a-proxmox-vm-with-docker-compose-with-the-help-of-ai","status":"publish","type":"post","link":"https:\/\/muhammadsoliman.com\/index.php\/2026\/05\/29\/build-traefik-on-a-proxmox-vm-with-docker-compose-with-the-help-of-ai\/","title":{"rendered":"Build Traefik on a Proxmox VM with Docker Compose with the help of AI"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">the Goal<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">We are building a clean Docker gateway VM inside Proxmox.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This VM will later run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Docker\nTraefik\nPortainer\nn8n\nPostgreSQL\nCloudflare wildcard SSL\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Important design rule:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Do not install Docker, Traefik, directly on the Proxmox host.\nKeep the Proxmox host clean.\nRun services inside VMs.\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Architecture<\/h1>\n\n\n\n<pre class=\"wp-block-code\"><code>Hetzner Dedicated Server\n        \u2193\nProxmox Host\n        \u2193\nVM 100: docker-gateway\n        \u2193\nDocker Engine\n        \u2193\nTraefik container\n        \n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">VM 100 uses a private network:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Proxmox vmbr1: 10.10.100.1\/24\nVM 100:        10.10.100.100\/24\nGateway:       10.10.100.1\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Traefik runs inside VM 100, not on the Proxmox host.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Security Strategy<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">For the first setup:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Hetzner firewall: ON\nProxmox firewall: OFF\nTraefik: reverse proxy only\nPublic TCP 80\/443: do not open until Traefik is ready\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Traefik will later control:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>HTTP 80\nHTTPS 443\ndomain routing\nSSL certificates\nredirects\nmiddlewares\ndashboard protection\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Traefik does <strong>not<\/strong> control:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>SSH\nProxmox firewall\nHetzner firewall\nJitsi UDP 10000\nTURN ports\nLinux kernel firewall\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Step 1 \u2014 Create VM 100 in Proxmox<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Recommended VM settings:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>VM ID: 100\nName: docker-gateway\nOS: Debian 13 minimal\nCPU: 4 cores\nRAM: 8 GB\nDisk: 200 GB\nStorage: local-zfs\nNetwork: vmbr1\nModel: VirtIO\nFirewall: OFF\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Use Debian minimal install.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">During install, select only:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>standard system utilities\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If there is no DHCP, continue without network and configure static networking later.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Step 2 \u2014 Create Private Bridge <code>vmbr1<\/code> on Proxmox<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">In Proxmox UI:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>pve1 \u2192 System \u2192 Network \u2192 Create \u2192 Linux Bridge\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Use:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Name: vmbr1\nIPv4\/CIDR: 10.10.100.1\/24\nGateway: blank\nBridge ports: blank\nAutostart: checked\nVLAN aware: unchecked\nComment: Private VM NAT network\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Do not edit <code>vmbr0<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Apply configuration.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Step 3 \u2014 Connect VM 100 to <code>vmbr1<\/code><\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">In Proxmox:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>VM 100 \u2192 Hardware \u2192 Network Device \u2192 Edit\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Set:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Bridge: vmbr1\nModel: VirtIO\nFirewall: OFF\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Reboot VM 100.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Step 4 \u2014 Configure Static IP inside VM 100<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">RUN THIS ON: VM 100 console<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Become root:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>su -\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Edit network config:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>nano \/etc\/network\/interfaces\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Use:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>auto lo\niface lo inet loopback\n\nauto ens18\niface ens18 inet static\n    address 10.10.100.100\/24\n    gateway 10.10.100.1\n    dns-nameservers 1.1.1.1 8.8.8.8\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Save:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>CTRL + O\nEnter\nCTRL + X\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Reboot:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>reboot\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Test private gateway:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ping -c 3 10.10.100.1\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Success:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>3 packets transmitted, 3 received\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Step 5 \u2014 Add Proxmox NAT for VM Internet<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">RUN THIS ON: Proxmox host over SSH<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">From Mac Terminal:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ssh proxmox\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Then on Proxmox:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sysctl -w net.ipv4.ip_forward=1\n<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>iptables -t nat -A POSTROUTING -s 10.10.100.0\/24 -o vmbr0 -j MASQUERADE\n<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>iptables -A FORWARD -i vmbr1 -o vmbr0 -s 10.10.100.0\/24 -j ACCEPT\n<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>iptables -A FORWARD -i vmbr0 -o vmbr1 -d 10.10.100.0\/24 -m state --state RELATED,ESTABLISHED -j ACCEPT\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Test from VM 100:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ping -c 3 1.1.1.1\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Success:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>3 packets transmitted, 3 received\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Step 6 \u2014 Fix DNS inside VM 100<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Important lesson learned:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The correct file is:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/etc\/resolv.conf\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Not:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/etc\/resolve.conf\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If DNS fails, check:<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">RUN THIS ON: VM 100 console<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>ls -la \/etc\/resolv.conf \/etc\/resolve.conf 2&gt;&amp;1\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Correct DNS file:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>nano \/etc\/resolv.conf\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Use:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>nameserver 1.1.1.1\nnameserver 8.8.8.8\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Test:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ping -c 3 deb.debian.org\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Success:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>3 packets transmitted, 3 received\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Step 7 \u2014 Fix Debian APT Sources<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Because Debian was installed without a network mirror, fix:<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">RUN THIS ON: VM 100 console<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>nano \/etc\/apt\/sources.list\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Use:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># deb cdrom:&#91;Debian GNU\/Linux 13.5.0 _Trixie_ - Official amd64 NETINST with firmware]\/ trixie contrib main non-free-firmware\n\ndeb http:\/\/deb.debian.org\/debian trixie main contrib non-free-firmware\ndeb http:\/\/deb.debian.org\/debian trixie-updates main contrib non-free-firmware\ndeb http:\/\/security.debian.org\/debian-security trixie-security main contrib non-free-firmware\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>apt update\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Then:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>apt upgrade -y\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Step 8 \u2014 Install Basic Tools<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">RUN THIS ON: VM 100 console<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>apt install -y sudo openssh-server curl ca-certificates gnupg qemu-guest-agent\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Enable services:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>systemctl enable --now ssh\nsystemctl enable --now qemu-guest-agent\nusermod -aG sudo dockeradmin\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Reboot:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>reboot\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Log in again and test:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo whoami\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Success:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>root\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Step 9 \u2014 SSH into VM 100<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">From your Mac:<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">RUN THIS ON: Mac Terminal<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>ssh proxmox\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Then:<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">RUN THIS ON: Proxmox host over SSH<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>ssh dockeradmin@10.10.100.100\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Success prompt:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>dockeradmin@docker-gatway:~$\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Step 10 \u2014 Install Official Docker Engine<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">RUN THIS ON: VM 100 over SSH<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo apt update\n<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo apt install -y ca-certificates curl gnupg\n<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo install -m 0755 -d \/etc\/apt\/keyrings\n<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>curl -fsSL https:\/\/download.docker.com\/linux\/debian\/gpg | sudo gpg --dearmor -o \/etc\/apt\/keyrings\/docker.gpg\n<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo chmod a+r \/etc\/apt\/keyrings\/docker.gpg\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Add Docker repo:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>echo \\\n  \"deb &#91;arch=$(dpkg --print-architecture) signed-by=\/etc\/apt\/keyrings\/docker.gpg] https:\/\/download.docker.com\/linux\/debian \\\n  $(. \/etc\/os-release &amp;&amp; echo \"$VERSION_CODENAME\") stable\" | \\\n  sudo tee \/etc\/apt\/sources.list.d\/docker.list &gt; \/dev\/null\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Update:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo apt update\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Install Docker:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Test:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo docker run hello-world\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Success:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Hello from Docker!\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Step 11 \u2014 Use Docker Without Sudo<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">RUN THIS ON: VM 100 over SSH<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo usermod -aG docker dockeradmin\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Refresh session:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>newgrp docker\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Test:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>docker ps\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Success:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>CONTAINER ID   IMAGE   COMMAND   CREATED   STATUS   PORTS   NAMES\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Test Compose:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>docker compose version\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Step 12 \u2014 Create Docker Folder Structure<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">RUN THIS ON: VM 100 over SSH<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo mkdir -p \/opt\/docker\/{traefik,portainer,n8n,postgres}\n<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo chown -R dockeradmin:dockeradmin \/opt\/docker\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Create Traefik network:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>docker network create proxy\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Confirm:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>docker network ls\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Success:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>proxy\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Step 13 \u2014 Make Proxmox NAT Persistent<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">RUN THIS ON: Proxmox host over SSH<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Create sysctl file:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cat &gt; \/etc\/sysctl.d\/99-vmbr1-nat.conf &lt;&lt; 'EOF'\nnet.ipv4.ip_forward=1\nEOF\n\nsysctl --system\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Create NAT script:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cat &gt; \/usr\/local\/sbin\/vmbr1-nat.sh &lt;&lt; 'EOF'\n#!\/bin\/sh\nset -eu\n\nLAN_CIDR=\"10.10.100.0\/24\"\nLAN_IF=\"vmbr1\"\nWAN_IF=\"vmbr0\"\n\ncase \"${1:-start}\" in\n  start)\n    sysctl -w net.ipv4.ip_forward=1 &gt;\/dev\/null\n\n    iptables -t nat -C POSTROUTING -s \"$LAN_CIDR\" -o \"$WAN_IF\" -j MASQUERADE 2&gt;\/dev\/null || \\\n      iptables -t nat -A POSTROUTING -s \"$LAN_CIDR\" -o \"$WAN_IF\" -j MASQUERADE\n\n    iptables -C FORWARD -s \"$LAN_CIDR\" -i \"$LAN_IF\" -o \"$WAN_IF\" -j ACCEPT 2&gt;\/dev\/null || \\\n      iptables -A FORWARD -s \"$LAN_CIDR\" -i \"$LAN_IF\" -o \"$WAN_IF\" -j ACCEPT\n\n    iptables -C FORWARD -d \"$LAN_CIDR\" -i \"$WAN_IF\" -o \"$LAN_IF\" -m state --state RELATED,ESTABLISHED -j ACCEPT 2&gt;\/dev\/null || \\\n      iptables -A FORWARD -d \"$LAN_CIDR\" -i \"$WAN_IF\" -o \"$LAN_IF\" -m state --state RELATED,ESTABLISHED -j ACCEPT\n    ;;\n\n  stop)\n    iptables -t nat -D POSTROUTING -s \"$LAN_CIDR\" -o \"$WAN_IF\" -j MASQUERADE 2&gt;\/dev\/null || true\n    iptables -D FORWARD -s \"$LAN_CIDR\" -i \"$LAN_IF\" -o \"$WAN_IF\" -j ACCEPT 2&gt;\/dev\/null || true\n    iptables -D FORWARD -d \"$LAN_CIDR\" -i \"$WAN_IF\" -o \"$LAN_IF\" -m state --state RELATED,ESTABLISHED -j ACCEPT 2&gt;\/dev\/null || true\n    ;;\n\n  *)\n    echo \"Usage: $0 {start|stop}\"\n    exit 1\n    ;;\nesac\nEOF\n\nchmod +x \/usr\/local\/sbin\/vmbr1-nat.sh\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Create service:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cat &gt; \/etc\/systemd\/system\/vmbr1-nat.service &lt;&lt; 'EOF'\n&#91;Unit]\nDescription=Persistent NAT for Proxmox private VM network vmbr1\nAfter=network-online.target\nWants=network-online.target\n\n&#91;Service]\nType=oneshot\nExecStart=\/usr\/local\/sbin\/vmbr1-nat.sh start\nExecStop=\/usr\/local\/sbin\/vmbr1-nat.sh stop\nRemainAfterExit=yes\n\n&#91;Install]\nWantedBy=multi-user.target\nEOF\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Enable:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>systemctl daemon-reload\nsystemctl enable --now vmbr1-nat.service\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Check:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>systemctl status vmbr1-nat.service --no-pager\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Success:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Active: active (exited)\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Step 14 \u2014 Create Traefik YAML Files<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">RUN THIS ON: VM 100 over SSH<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>mkdir -p \/opt\/docker\/traefik\/data\n\ntouch \/opt\/docker\/traefik\/docker-compose.yaml\ntouch \/opt\/docker\/traefik\/data\/traefik.yml\ntouch \/opt\/docker\/traefik\/data\/config.yml\ntouch \/opt\/docker\/traefik\/data\/acme.json\n\nchmod 600 \/opt\/docker\/traefik\/data\/acme.json\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Confirm:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ls -la \/opt\/docker\/traefik\nls -la \/opt\/docker\/traefik\/data\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Step 15 \u2014 Create <code>traefik.yml<\/code><\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">RUN THIS ON: VM 100 over SSH<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>cat &gt; \/opt\/docker\/traefik\/data\/traefik.yml &lt;&lt; 'EOF'\napi:\n  dashboard: true\n\nentryPoints:\n  http:\n    address: \":80\"\n  https:\n    address: \":443\"\n\nproviders:\n  docker:\n    endpoint: \"unix:\/\/\/var\/run\/docker.sock\"\n    exposedByDefault: false\n  file:\n    filename: \/config.yml\n    watch: true\n\nlog:\n  level: INFO\n\naccessLog: {}\nEOF\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Step 16 \u2014 Create <code>config.yml<\/code><\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">RUN THIS ON: VM 100 over SSH<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>cat &gt; \/opt\/docker\/traefik\/data\/config.yml &lt;&lt; 'EOF'\nhttp:\n  middlewares:\n    security-headers:\n      headers:\n        frameDeny: true\n        contentTypeNosniff: true\n        browserXssFilter: true\n        referrerPolicy: \"strict-origin-when-cross-origin\"\n        customRequestHeaders:\n          X-Forwarded-Proto: \"https\"\n\n    https-redirect:\n      redirectScheme:\n        scheme: https\n        permanent: true\nEOF\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Step 17 \u2014 Create Traefik <code>docker-compose.yaml<\/code><\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">RUN THIS ON: VM 100 over SSH<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>cat &gt; \/opt\/docker\/traefik\/docker-compose.yaml &lt;&lt; 'EOF'\nservices:\n  traefik:\n    image: traefik:v3.6.2\n    container_name: traefik\n    restart: unless-stopped\n    security_opt:\n      - no-new-privileges:true\n    networks:\n      - proxy\n    ports:\n      - \"80:80\"\n      - \"443:443\"\n    volumes:\n      - \/etc\/localtime:\/etc\/localtime:ro\n      - \/var\/run\/docker.sock:\/var\/run\/docker.sock:ro\n      - .\/data\/traefik.yml:\/traefik.yml:ro\n      - .\/data\/config.yml:\/config.yml:ro\n      - .\/data\/acme.json:\/acme.json\n\nnetworks:\n  proxy:\n    external: true\nEOF\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Validate:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cd \/opt\/docker\/traefik\ndocker compose config\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Success:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>No YAML error\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Step 18 \u2014 Start Traefik<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">RUN THIS ON: VM 100 over SSH<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Make sure you are inside:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/opt\/docker\/traefik\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>docker compose up -d\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Check:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>docker ps\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Success:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>traefik   Up\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Check logs:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>docker logs traefik --tail=50\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Success:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Traefik version 3.6.2\nStarting provider *file.Provider\nStarting provider *traefik.Provider\nStarting provider *docker.Provider\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Important Issue We Hit<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Using:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>traefik:v3.0\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">caused this error:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>client version 1.24 is too old. Minimum supported API version is 1.40\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Fix:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sed -i 's\/image: traefik:v3.0\/image: traefik:v3.6.2\/' \/opt\/docker\/traefik\/docker-compose.yaml\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Then:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cd \/opt\/docker\/traefik\ndocker compose pull\ndocker compose up -d\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">After switching to:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>traefik:v3.6.2\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Traefik started cleanly.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Current Status<\/h1>\n\n\n\n<pre class=\"wp-block-code\"><code>VM 100 installed\nPrivate NAT works\nPersistent NAT service active\nDNS works\nAPT works\nDocker installed\nDocker Compose installed\nDocker works without sudo\nTraefik files created\nTraefik running as container\nTraefik v3.6.2 works cleanly\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Traefik is listening inside VM 100 on:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>80\n443\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">But it is not publicly exposed yet because we have not opened public TCP 80\/443 in Hetzner firewall.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Next Steps<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">After this tutorial stage:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>1. Install Portainer with YAML\n2. Add protected subdomain: portainer.&#91;DOMAIN]\n3. Add protected subdomain: traefik.&#91;DOMAIN]\n4. Add n8n with PostgreSQL\n5. Add Cloudflare DNS challenge\n6. Add wildcard SSL certificate\n7. Open TCP 80\/443 in Hetzner firewall only when ready\n8. Add WireGuard later for private admin access\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Recommended subdomains:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>traefik.&#91;DOMAIN]\nportainer.&#91;DOMAIN]\nn8n.&#91;DOMAIN]\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Do not expose dashboards publicly without protection.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Use:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>strong app passwords\nbasic auth where needed\nCloudflare protection if desired\nVPN\/IP allowlist later\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n","protected":false},"excerpt":{"rendered":"<p>the Goal We are building a clean Docker gateway VM inside Proxmox. This VM will later&#8230;<\/p>\n","protected":false},"author":1,"featured_media":3284,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_kad_blocks_custom_css":"","_kad_blocks_head_custom_js":"","_kad_blocks_body_custom_js":"","_kad_blocks_footer_custom_js":"","_kadence_starter_templates_imported_post":false,"_kad_post_transparent":"","_kad_post_title":"","_kad_post_layout":"","_kad_post_sidebar_id":"","_kad_post_content_style":"","_kad_post_vertical_padding":"","_kad_post_feature":"","_kad_post_feature_position":"above","_kad_post_header":false,"_kad_post_footer":false,"_kad_post_classname":"","footnotes":""},"categories":[28,31,32,30],"tags":[],"class_list":["post-3283","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-docker","category-linux","category-proxmox","category-server"],"taxonomy_info":{"category":[{"value":28,"label":"Docker"},{"value":31,"label":"Linux"},{"value":32,"label":"proxmox"},{"value":30,"label":"Server"}]},"featured_image_src_large":["https:\/\/muhammadsoliman.com\/wp-content\/uploads\/2026\/05\/ChatGPT-Image-May-29-2026-01_55_32-AM-1024x1024.png",1024,1024,true],"author_info":{"display_name":"Muhammad Soliman","author_link":"https:\/\/muhammadsoliman.com\/author\/muhmmad-soliman\/"},"comment_info":0,"category_info":[{"term_id":28,"name":"Docker","slug":"docker","term_group":0,"term_taxonomy_id":28,"taxonomy":"category","description":"","parent":0,"count":2,"filter":"raw","cat_ID":28,"category_count":2,"category_description":"","cat_name":"Docker","category_nicename":"docker","category_parent":0},{"term_id":31,"name":"Linux","slug":"linux","term_group":0,"term_taxonomy_id":31,"taxonomy":"category","description":"","parent":0,"count":2,"filter":"raw","cat_ID":31,"category_count":2,"category_description":"","cat_name":"Linux","category_nicename":"linux","category_parent":0},{"term_id":32,"name":"proxmox","slug":"proxmox","term_group":0,"term_taxonomy_id":32,"taxonomy":"category","description":"","parent":0,"count":1,"filter":"raw","cat_ID":32,"category_count":1,"category_description":"","cat_name":"proxmox","category_nicename":"proxmox","category_parent":0},{"term_id":30,"name":"Server","slug":"server","term_group":0,"term_taxonomy_id":30,"taxonomy":"category","description":"","parent":0,"count":2,"filter":"raw","cat_ID":30,"category_count":2,"category_description":"","cat_name":"Server","category_nicename":"server","category_parent":0}],"tag_info":false,"_links":{"self":[{"href":"https:\/\/muhammadsoliman.com\/index.php\/wp-json\/wp\/v2\/posts\/3283","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/muhammadsoliman.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/muhammadsoliman.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/muhammadsoliman.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/muhammadsoliman.com\/index.php\/wp-json\/wp\/v2\/comments?post=3283"}],"version-history":[{"count":3,"href":"https:\/\/muhammadsoliman.com\/index.php\/wp-json\/wp\/v2\/posts\/3283\/revisions"}],"predecessor-version":[{"id":3288,"href":"https:\/\/muhammadsoliman.com\/index.php\/wp-json\/wp\/v2\/posts\/3283\/revisions\/3288"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/muhammadsoliman.com\/index.php\/wp-json\/wp\/v2\/media\/3284"}],"wp:attachment":[{"href":"https:\/\/muhammadsoliman.com\/index.php\/wp-json\/wp\/v2\/media?parent=3283"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/muhammadsoliman.com\/index.php\/wp-json\/wp\/v2\/categories?post=3283"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/muhammadsoliman.com\/index.php\/wp-json\/wp\/v2\/tags?post=3283"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}