<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[BeeDog's Tech Pit]]></title><description><![CDATA[BeeDog's Tech Pit]]></description><link>https://beedog.cc</link><generator>RSS for Node</generator><lastBuildDate>Thu, 09 Apr 2026 06:35:07 GMT</lastBuildDate><atom:link href="https://beedog.cc/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Portainer with Traefik, automatic HTTPS and protecting your online services with CloudFlare Access in a home lab]]></title><description><![CDATA[This post documents some parts of my home lab setup on which I run a bunch of VM and container workload. These may not be the best setup but they work for me, and hopefully it will help you too. Every piece of software and service mentioned is free, ...]]></description><link>https://beedog.cc/portainer-with-traefik-automatic-https-and-protecting-your-online-services-with-cloudflare-access-in-a-home-lab</link><guid isPermaLink="true">https://beedog.cc/portainer-with-traefik-automatic-https-and-protecting-your-online-services-with-cloudflare-access-in-a-home-lab</guid><category><![CDATA[Traefik]]></category><category><![CDATA[Portainer]]></category><category><![CDATA[Docker compose]]></category><category><![CDATA[cloudflare]]></category><dc:creator><![CDATA[Norm Dong]]></dc:creator><pubDate>Sun, 26 Jan 2025 06:08:58 GMT</pubDate><content:encoded><![CDATA[<p>This post documents some parts of my home lab setup on which I run a bunch of VM and container workload. These may not be the best setup but they work for me, and hopefully it will help you too. Every piece of software and service mentioned is free, except for the domain name.</p>
<p>I use the <code>docker-compose.yml</code> file from <a target="_blank" href="https://docs.portainer.io/advanced/reverse-proxy/traefik">Portainer's doc</a> to installing Portainer with Traefik. On top of it, I have a few extra things:</p>
<pre><code class="lang-yaml">    <span class="hljs-comment"># For Traefik container</span>
    <span class="hljs-attr">command:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">--api=true</span> <span class="hljs-comment"># Enable the dashboard</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">--api.dashboard=true</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">--api.insecure=true</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">--providers.file.filename=/etc/traefik/dynamic.yml</span> <span class="hljs-comment"># Add a dynamic config file</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">--providers.file.watch=true</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-comment"># The dynamic config file sits in the same folder with the docker-compose.yaml file</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"./dynamic.yml:/etc/traefik/dynamic.yml:ro"</span>
</code></pre>
<p>For container workloads, I tend to run them as a docker-compose stack. I add the same labels as the Portainer's docker compose file. Specifically:</p>
<pre><code class="lang-yaml">    <span class="hljs-attr">networks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">xxxx</span> <span class="hljs-comment"># Remember to put the container in the same network as Traefik</span>
    <span class="hljs-attr">labels:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"traefik.enable=true"</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"traefik.http.routers.servicename.rule=Host(`sub.domain.notcom`)"</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"traefik.http.routers.servicename.entrypoints=websecure"</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"traefik.http.routers.servicename.service=servicename"</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"traefik.http.routers.servicename.tls.certresolver=leresolver"</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"traefik.http.services.servicename.loadbalancer.server.port=xxxx"</span> <span class="hljs-comment"># The port on the container, NOT the mapped port on the host</span>
</code></pre>
<p>I run <a target="_blank" href="https://kasmweb.com/">Kasm Workspaces</a> on its own VM, its web UI uses a self-signed certificate, I need to tell Traefik to not verify that certificate in my dynamic config file:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">http:</span>
  <span class="hljs-attr">routers:</span>
    <span class="hljs-attr">kasm-route:</span>
      <span class="hljs-attr">rule:</span> <span class="hljs-string">"Host(`sub.domain.notcom`)"</span>
      <span class="hljs-attr">service:</span> <span class="hljs-string">kasm</span>
      <span class="hljs-attr">entryPoints:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">websecure</span>
      <span class="hljs-attr">tls:</span>
        <span class="hljs-attr">certresolver:</span> <span class="hljs-string">"leresolver"</span>

  <span class="hljs-attr">services:</span>
    <span class="hljs-attr">kasm:</span>
      <span class="hljs-attr">loadBalancer:</span>
        <span class="hljs-attr">servers:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">url:</span> <span class="hljs-string">"https://10.10.10.10"</span>
        <span class="hljs-attr">serversTransport:</span> <span class="hljs-string">kasmTransport</span>

  <span class="hljs-attr">serversTransports:</span>
    <span class="hljs-attr">kasmTransport:</span>
      <span class="hljs-attr">insecureSkipVerify:</span> <span class="hljs-literal">true</span> <span class="hljs-comment"># Internal Server Error if self-signed certificate is not skipped</span>
</code></pre>
<p>I use CloudFlare Access to protect some of the services. In this particular setup, I add GitHub to my login methods, I won't get into the details of the setup, as of now it's under "Zero Trust" -&gt; "Settings" -&gt; "Authentication" -&gt; "Login methods" -&gt; "Add new" -&gt; select "GitHub". CloudFlare shows detailed steps of setting it up.</p>
<p>Then, go back to "Zero Trust" -&gt; "Access" -&gt; "Applications", setup a self-hosted application on the domain. For the access policy, it can be as simple as a single Include rule:
Selector: Emails.
Value: The email address of the GitHub account you are going to login with. We wouldn't want to let anyone who has a GitHub account in, would we?</p>
<p>Because Traefik automatically sets up HTTPS on the server with Let's Encrypt, you can set CloudFlare to full strict mode, rather than full - which does encrypt the traffic between CloudFlare and your server but does not verify the server's certificate.</p>
<p>That's it!</p>
]]></content:encoded></item><item><title><![CDATA[Managing Proxmox VMs and LXCs with Terraform]]></title><description><![CDATA[There are a few Proxmox provider implementations available on the Terraform registry, this post specifically uses this one: https://registry.terraform.io/providers/loeken/proxmox/latest/docs
Other implementations might behave slightly differently but...]]></description><link>https://beedog.cc/managing-proxmox-vms-and-lxcs-with-terraform</link><guid isPermaLink="true">https://beedog.cc/managing-proxmox-vms-and-lxcs-with-terraform</guid><category><![CDATA[proxmox]]></category><category><![CDATA[#IaC]]></category><category><![CDATA[Terraform]]></category><dc:creator><![CDATA[Norm Dong]]></dc:creator><pubDate>Fri, 17 Jan 2025 01:59:37 GMT</pubDate><content:encoded><![CDATA[<p>There are a few Proxmox provider implementations available on the Terraform registry, this post specifically uses this one: https://registry.terraform.io/providers/loeken/proxmox/latest/docs</p>
<p>Other implementations might behave slightly differently but I haven't tried. This particular implementation has a weird behavior: it tries to recreate the disk for a virtual machine when running <code>terraform apply</code> - which then fails on the server's side. Fortunately it doesn't stop me from creating more resources e.g. when I add a new LXC to the file, it will still create the LXC just fine, so it kinda "works". A new disk volume will be created and scheduled to replace a VM's disk if it's running (since it doesn't hot swap), which needs to be manually reverted and deleted.</p>
<p>Compared to other services that support IaC (e.g. an AWS EC2 instance), I'm not sure if it's this particular implementation, or if it's the API on Proxmox's side, the whole experience does not feel smooth. It wants to do some "updates" when I haven't changed a single letter, which then doesn't actually seem to change anything, again it mostly "works", and it's better than manually clicking through a few steps each time I want to create a new VM/LXC through the web UI, so it's good enough to me.</p>
<p>My Terraform file looks like this, hopefully it's helpful to you if you are trying to do something similar:</p>
<pre><code class="lang-hcl">terraform {
  required_providers {
    proxmox = {
      source  = "loeken/proxmox"
      version = "&gt;=2.9.0"
    }
  }
  required_version = "&gt;= 0.14"
}

provider "proxmox" {
  # Default HTTPS port of Proxmox is 8006, yours might be different
  pm_api_url = "https://xxxx:8006/api2/json"
  # I thought this was required as my Proxmox server uses a self-signed certificate. But apparently it works without this set to true anyway
  #pm_tls_insecure = true
  # Obviously not the best practice, you should use environment variables PM_USER and PM_PASS instead. I only do this because I am in a home lab environment
  pm_user     = "root@pam"
  pm_password = "secureultrapluspromax"
}

# https://registry.terraform.io/providers/loeken/proxmox/latest/docs/resources/vm_qemu
resource "proxmox_vm_qemu" "resource_name_here" {
  name        = "VM name here"
  target_node = "Name of the node (under the Datacenter node)"
  vmid        = 200
  cores       = 16
  memory      = 65536 # MiB
  os_type     = "ubuntu"
  # The volume name under the target node "local", then the storage type "ISO Images", then the image's name
  iso         = "local:iso/ubuntu-24.10-live-server-amd64.iso"

  disk {
    type    = "scsi"
    size    = "50G"
    storage = "local-lvm"
  }

  network {
    bridge    = "vmbr0"
    firewall  = false
    link_down = false
    model     = "e1000"
  }
}

# https://registry.terraform.io/providers/loeken/proxmox/latest/docs/resources/lxc
resource "proxmox_lxc" "resource_name_here" {
  target_node = "Name of the node (under the Datacenter node)"
  hostname     = "hostname"
  # The volume name under the target node "local", then the storage type "CT Templates", then the template's name
  ostemplate   = "local:vztmpl/ubuntu-22.04-standard_22.04-1_amd64.tar.zst"
  # Root password
  password     = "secureultrapluspromax"
  unprivileged = true
  vmid         = 201
  cores        = 4
  memory       = 4096 # MiB

  // Terraform will crash without rootfs defined
  rootfs {
    storage = "local-lvm"
    size    = "2G"
  }

  features {
    mount = "nfs"
  }

  # Comments from the provider's doc, I keep it here to remind myself of this weird bug
  # // NFS share mounted on host
  # // Without 'volume' defined, Proxmox will try to create a volume with
  # // the value of 'storage' + : + 'size' (without the trailing G) - e.g.
  # // "/srv/host/bind-mount-point:256".
  # // This behaviour looks to be caused by a bug in the provider.
  mountpoint {
    key     = "0"
    slot    = 0
    storage = "/mnt/mountpoint"
    volume  = "/mnt/mountpoint"
    mp      = "/mnt/data"
    # Unintuitively (and if I remember correctly), this does not work without specifying a size
    size    = "1T"
  }

  network {
    name   = "eth0"
    bridge = "vmbr0"
    ip     = "dhcp"
  }
}
</code></pre>
]]></content:encoded></item><item><title><![CDATA[Learning Ansible, Proxmox and LXC, Part 1]]></title><description><![CDATA[I've been running a server at home for a few years and gone through many iterations of hardware and different software/virtualization stacks. On this iteration I landed on Proxmox which supports LXC, an interesting virtualization technology.
Now, I h...]]></description><link>https://beedog.cc/learning-ansible-proxmox-and-lxc-part-1</link><guid isPermaLink="true">https://beedog.cc/learning-ansible-proxmox-and-lxc-part-1</guid><category><![CDATA[ansible]]></category><category><![CDATA[proxmox]]></category><category><![CDATA[#IaC]]></category><category><![CDATA[LXC]]></category><dc:creator><![CDATA[Norm Dong]]></dc:creator><pubDate>Sun, 27 Oct 2024 04:51:54 GMT</pubDate><content:encoded><![CDATA[<p>I've been running a server at home for a few years and gone through many iterations of hardware and different software/virtualization stacks. On this iteration I landed on Proxmox which supports LXC, an interesting virtualization technology.</p>
<p>Now, I have been a fan of IaC and I wanted to learn Ansible for a while. I'd prefer Terraform but the Terraform module for Proxmox did not work very well when I tried it a while ago, so it's a good opportunity for me to try Ansible!</p>
<p>This blog series is my learning notes for using Ansible to provision LXCs (and a few VMs) and configure the software/service running in said LXCs.</p>
<p>Or so I thought - the series is no more, but I'm keeping the "Part 1" in the title just for the lols, here is what happened:</p>
<p>Step 1, I create a snippet to quickly get an LXC up, it looks like this:</p>
<pre><code class="lang-yaml"><span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">test</span> <span class="hljs-string">playbook</span>
  <span class="hljs-attr">hosts:</span> <span class="hljs-string">localhost</span>

  <span class="hljs-attr">tasks:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Create</span> <span class="hljs-string">new</span> <span class="hljs-string">container</span> <span class="hljs-string">with</span> <span class="hljs-string">minimal</span> <span class="hljs-string">options</span>
    <span class="hljs-attr">community.general.proxmox:</span>
      <span class="hljs-attr">vmid:</span> <span class="hljs-number">200</span>
      <span class="hljs-attr">node:</span> <span class="hljs-string">my-proxmox-node</span>
      <span class="hljs-attr">api_user:</span> <span class="hljs-string">ansible-usr@pve</span>
      <span class="hljs-attr">api_password:</span> <span class="hljs-number">123</span>
      <span class="hljs-attr">api_host:</span> <span class="hljs-number">192.168</span><span class="hljs-string">.x.x</span>
      <span class="hljs-attr">hostname:</span> <span class="hljs-string">test-lxc</span>
      <span class="hljs-attr">password:</span> <span class="hljs-number">456</span>
      <span class="hljs-attr">ostemplate:</span> <span class="hljs-string">'local:vztmpl/ubuntu-24.04-standard_24.04-2_amd64.tar.zst'</span>
      <span class="hljs-attr">storage:</span> <span class="hljs-string">local-lvm</span>
</code></pre>
<p>Then I run <code>ansible-playbook playbook.yml</code> to create the LXC. So far, so good.</p>
<p>Step 2, the LXC is up and running but the config is quite loose. I'd like to tighten it up a bit. I add <code>disk_volume</code> block to the template, replacing <code>storage</code>. As per <a target="_blank" href="https://docs.ansible.com/ansible/latest/collections/community/general/proxmox_module.html#examples">example in the documentation</a></p>
<pre><code class="lang-yaml"><span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">test</span> <span class="hljs-string">playbook</span>
  <span class="hljs-attr">hosts:</span> <span class="hljs-string">localhost</span>

  <span class="hljs-attr">tasks:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Create</span> <span class="hljs-string">new</span> <span class="hljs-string">container</span> <span class="hljs-string">with</span> <span class="hljs-string">minimal</span> <span class="hljs-string">options</span>
    <span class="hljs-attr">community.general.proxmox:</span>
      <span class="hljs-attr">vmid:</span> <span class="hljs-number">200</span>
      <span class="hljs-attr">node:</span> <span class="hljs-string">my-proxmox-node</span>
      <span class="hljs-attr">api_user:</span> <span class="hljs-string">ansible-usr@pve</span>
      <span class="hljs-attr">api_password:</span> <span class="hljs-number">123</span>
      <span class="hljs-attr">api_host:</span> <span class="hljs-number">192.168</span><span class="hljs-string">.x.x</span>
      <span class="hljs-attr">hostname:</span> <span class="hljs-string">test-lxc</span>
      <span class="hljs-attr">password:</span> <span class="hljs-number">456</span>
      <span class="hljs-attr">ostemplate:</span> <span class="hljs-string">'local:vztmpl/ubuntu-24.04-standard_24.04-2_amd64.tar.zst'</span>
      <span class="hljs-attr">disk_volume:</span>
        <span class="hljs-attr">storage:</span> <span class="hljs-string">local-lvm</span>
        <span class="hljs-attr">size:</span> <span class="hljs-number">20</span>
</code></pre>
<p>What could possibly go wrong?</p>
<p>Step 3, Ansible complains: "disk_volume is not a valid parameter". Huh? But the <a target="_blank" href="https://docs.ansible.com/ansible/latest/collections/community/general/proxmox_module.html#parameter-disk_volume">doc</a> contains the definition and examples, how could...oh, "added in community.general 9.2.0", I see. Let me upgrade my local Ansible version.</p>
<p>Step 3.5 I'll spare you (and myself!) the details of me trying to figure out my local module's version, things are already bad enough.</p>
<p>Step 4, now the error message is something like "400 Bad Request, disk_volume is not defined in schema". OK, maybe I need to upgrade and reboot my Proxmox installation as well. Too bad though, next step please!</p>
<p>Step 5, OK I've given up on trying to make <code>disk_volume</code> work. Let me revert that property and move on to the next thing, adding <code>cores</code> property to limit the CPU resource.</p>
<pre><code class="lang-yaml"><span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">test</span> <span class="hljs-string">playbook</span>
  <span class="hljs-attr">hosts:</span> <span class="hljs-string">localhost</span>

  <span class="hljs-attr">tasks:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Create</span> <span class="hljs-string">new</span> <span class="hljs-string">container</span> <span class="hljs-string">with</span> <span class="hljs-string">minimal</span> <span class="hljs-string">options</span>
    <span class="hljs-attr">community.general.proxmox:</span>
      <span class="hljs-attr">vmid:</span> <span class="hljs-number">200</span>
      <span class="hljs-attr">node:</span> <span class="hljs-string">my-proxmox-node</span>
      <span class="hljs-attr">api_user:</span> <span class="hljs-string">ansible-usr@pve</span>
      <span class="hljs-attr">api_password:</span> <span class="hljs-number">123</span>
      <span class="hljs-attr">api_host:</span> <span class="hljs-number">192.168</span><span class="hljs-string">.x.x</span>
      <span class="hljs-attr">hostname:</span> <span class="hljs-string">test-lxc</span>
      <span class="hljs-attr">password:</span> <span class="hljs-number">456</span>
      <span class="hljs-attr">ostemplate:</span> <span class="hljs-string">'local:vztmpl/ubuntu-24.04-standard_24.04-2_amd64.tar.zst'</span>
      <span class="hljs-attr">storage:</span> <span class="hljs-string">local-lvm</span>
      <span class="hljs-attr">cores:</span> <span class="hljs-number">4</span>
</code></pre>
<p>Works like a charm. If we just ignore the whole <code>disk_volume</code> thing for a second...</p>
<p>Step 6, I want to try updating the LXC through Ansible. I change <code>cores</code> from 4 to 6, save, rerun, surely this should update my LXC's CPU to 6, right? Right?</p>
<pre><code>PLAY RECAP **********************************************************************************************************************
localhost                  : ok=<span class="hljs-number">2</span>    changed=<span class="hljs-number">0</span>    unreachable=<span class="hljs-number">0</span>    failed=<span class="hljs-number">0</span>    skipped=<span class="hljs-number">0</span>    rescued=<span class="hljs-number">0</span>    ignored=<span class="hljs-number">0</span>
</code></pre><p>Step 7, I think it's time to revisit Terraform. Wish me luck!</p>
]]></content:encoded></item></channel></rss>