I cover simple steps to secure a Node.js web app exposed to the public internet on a VPS, including putting it behind an Nginx reverse proxy with basic auth, enforcing HTTPS, and using Fail2ban to automatically ban brute-force attempts. I also cover restricting ports via a cloud firewall, disabling root SSH login, using SSH keys, keeping the system updated, and basic monitoring with Uptime Kuma...
Because my Uptime Kuma monitoring instance runs on a home network sharing the same flaky 4G connection as the VPS sites it watches, I consider Uptime Robot before deploying a second instance on Fly.io’s free tier. Setting it up as a Docker container proves simple, and to fold the remote status back into my local instance I discover Uptime Kuma already exposes an SVG status badge usable with keyword monitoring, avoiding a custom scraping endpoint...
I walk through deploying a Vite/React app on GitHub Pages using the branch-based setup. I cover pointing Pages at a /docs directory, setting Vite’s base path to the repo name, building and copying the output to /docs, and an optional vite.config change to build directly into it...
A guide to automating the setup of new self-hosted services on Proxmox by converting a fully configured LXC container into a reusable template. I cover preparing the base container with standard software such as Docker and Tailscale, addressing clone-specific issues like host names, machine IDs, and SSH host keys, then cloning the template to spin up new containers quickly...
A security vulnerability announced for Forgejo, which also affects Gitea and Gogs, prompted me to look into the fork history behind my self-hosted Gogs setup: Gitea split from Gogs in 2016 over project management disagreements, and Forgejo forked from Gitea in 2022 after its trademarks moved to a company. Impressed by Forgejo’s handling of the issue and only lightly invested in Gogs, I’m considering a switch. The takeaway is that project governance matters alongside features, and security announcements for the tools you rely on are worth following...
A guide to pushing a single git repository to two remotes, prompted by my running a private Gogs instance alongside GitHub. I cover naming remotes meaningfully rather than relying on the convention of origin, pushing to each remote explicitly, and using the -u flag to set a default remote, which becomes the source of truth for both pushing and pulling. My typical setup treats Gogs as the source of truth, with manual pushes to GitHub when code needs to be shared...
A walkthrough of my workflow for adding a new self-hosted service to my Proxmox homelab, using audiobookshelf—set up after abandoning Audible—as the worked example. It covers running the service as a Docker container inside an unprivileged Debian LXC, mounting NAS storage via fstab, Tailscale access, Ansible automation, monitoring with Uptime Kuma, and Proxmox backups...
I look at structuring Ansible playbooks, starting with the distinction between plays and tasks, then covering how to import one playbook into another with import_playbook. I include a note on where imports can be placed — at the top level of the YAML, not inside a play...
I look at replacing a shell script that built and pushed separate Docker images for amd64 and arm64 with docker buildx, which produces a single multi-platform image of my mdserver app. My attempt to also target 32-bit ARM for the Raspberry Pi runs extremely slowly because buildx emulates foreign architectures with QEMU, prompting me to consider offloading builds to GitHub Actions...
I explain how to back up and migrate a Docker named volume between hosts using the official method: stopping the container, then running a temporary container that mounts the volume and tars its data to a file. I work through an example with Uptime Kuma that covers moving the data to a new VM and restoring it into a fresh install, including a gotcha where restoring to the wrong path leaves the data nested and ignored. I end by questioning why simply copying the volume’s files from the local filesystem isn’t recommended...
A follow-up to my previous post on tagging Proxmox guests, showing how to use Ansible to automatically start VMs and LXC containers before running apt updates. I cover installing the proxmoxer Python library, my inventory and vault setup, and the differences between starting VMs by name with the proxmox_kvm module and containers by VMID with the proxmox module. I include a complete example playbook...
I use Proxmox’s tagging system to mark which VMs and containers need to be powered on before running my weekend Ansible update script on a dev server. My walkthrough covers applying tags in the web GUI, customizing colors via Tag Style Override, the three display styles available, and sorting machines by tag in the Datacenter search view...
My weekend Ansible apt upgrade run failed on one Ubuntu host with a BADSIG GPG signature error on the Ubuntu jammy-updates repository. I trace the problem to my Apt Cacher NG cache and fix it by deleting the cached Ubuntu repository data, forcing fresh index files to be downloaded. I leave the underlying cause unresolved, though it appears to affect only that one host...
A walkthrough of how I add a new domain as a virtual host to an NGINX server already managed by Certbot, showing that rerunning certbot –nginx lets me expand the existing combined certificate to cover the new domain. I also cover migrating domains with existing Porkbun-issued certificates to the same server and folding them into the combined certificate, ending with a note on revoking the now-redundant ones...
I explain why I switched from manually downloading SSL certificates from PorkBun every 90 days to using Let’s Encrypt and Certbot, prompted in part by limitations with managing Australian .au domains. It covers installing Certbot on Ubuntu with NGINX, how it reads site configs to obtain and install certificates, and the systemd timer that renews them automatically...
While setting up an Ubuntu 20.04 LXC on Proxmox to run a TP-Link Omada controller, I found DNS resolution failing because Proxmox copies the host’s Tailscale-based resolv.conf into containers, and the container has no route to the Tailscale DNS address. In this post, I cover the diagnosis of why Debian templates behaved differently, plus two fixes: specifying DNS explicitly in the Proxmox GUI, or creating a .pve-ignore.resolv.conf file so Proxmox stops overwriting the container’s settings...
I set up APT Cacher NG in an unprivileged LXC container to cache Debian package downloads for machines on my homelab LAN, avoiding repeated fetching of the same updates. My walkthrough covers installing and configuring the server (including enabling HTTPS passthrough), pointing client machines at the cache via a proxy config file pushed out with Ansible, and checking the built-in report page for cache statistics...
I deploy a small Go monitoring endpoint to my homelab servers as a systemd service, using Ansible. I cover the basics of a systemd .service file, including target dependencies like After=network.target, and an Ansible playbook that copies the executable and service file into place, restarting the service via handlers only when files change. I pull my sudo credentials from an encrypted Ansible vault...
A small Go utility exposes a lightweight JSON endpoint on each node or VM indicating whether RAM and disk usage are okay. Uptime Kuma watches the endpoint with its HTTP keyword check and sends an ntfy push notification when a machine gets into trouble. I include brief impressions of writing in Go and link to the GitHub repo...
I found that backing up an unprivileged LXC container on Proxmox to an NFS share can fail with a permission denied error when the backup process tries to create a temporary file on the remote share. The fix is to edit /etc/vzdump.conf on my Proxmox node and set tmpdir to /tmp, so the temporary file is built locally before being copied to the share...