Using ntfy.sh, shell commands can trigger a push notification to a phone or watch via curl when they finish. I demonstrate chaining long-running commands like rsync with && and || to send success or failure messages, running everything in the background with nohup, and redirecting the curl output to a log file...
When my home internet connection can’t reach DockerHub, I work around it by pulling the Jellyfin image I need on a laptop tethered to a phone hotspot, then moving it to the server as a file using docker save and docker load. The post also includes a short explanation of how container images are built from layers described by a manifest...
Hosting UptimeKuma on Fly.io’s free tier, I find that updating an out-of-date container image requires nothing more than running fly deploy with an existing fly.toml file. After the update the app starts crashing from memory exhaustion, and switching to a lighter Alpine-based image appears to keep usage within the free instance’s 256MB limit...
How I make one Docker Compose service wait for another to be ready, using a healthcheck on the depended-on service and a depends_on condition with service_healthy on the dependent one. The worked example adds InfluxDB to my homelab monitoring stack so a Go metrics collector starts only after the database is up, with sample compose YAML included...
A BusyBox container serving a static site can’t fetch content over HTTPS because BusyBox ships without root CA certificates and has no package manager to install them. Rather than switch to a larger Alpine image, I settle on bind-mounting the host’s certificate directory into the container read-only, with example configurations for both docker-compose and docker run...
A follow-up on bundling static sites into BusyBox Docker containers, covering how to handle minor dynamic tasks such as periodically downloading an image into the directory of hosted static files. Rather than using cron, which behaves unexpectedly on BusyBox, my solution is a shell script with a download-and-sleep loop that runs in the background alongside the httpd server. I also note that BusyBox’s wget has TLS problems, a topic I defer to a later post...
Serving static websites from small Docker containers built on BusyBox httpd, coming in at around 4MB each. I walk through the Dockerfile setup, cross-platform builds from an M1 Mac to linux/amd64, pushing to the GitHub Container Registry, and deploying via docker-compose behind Nginx Proxy Manager, including DNS and SSL configuration...
I explain my move from Docker Hub to GitHub’s Container Registry for container images running on my VPSs, motivated by Docker Hub’s single-private-image free tier and the eventual possibility of CI/CD rebuilds. It walks through generating a Personal Access Token, logging in to ghcr.io, and pushing and pulling images with the registry included in the container name...
After abandoning an overly complicated LXD-based file transfer setup, I walk through using command-line rsync to sync files directly between two Synology NASes, including over Tailscale. I cover setting up passwordless SSH without ssh-copy-id, working around DSM 7’s restriction on Tailscale outbound connections and its lack of Magic DNS, enabling rsync in the Synology interface, excluding metadata and recycle bin directories, fixing permissions, throttling bandwidth, deleting remote files in one-way syncs, and running the job in the background with nohup...
Running NGINX Proxy Manager in a Docker container breaks the usual habit of pointing proxies at 127.0.0.1, since localhost now refers to inside the container rather than the host. I explain why, cover the confusing exception of NPM’s own admin interface, and show how joining service containers to NPM’s Docker network lets the proxy reach them by container name via DNS, with no ports exposed to the host...
I explain why I use Docker bind mounts instead of named Docker volumes in my homelab, keeping each container’s compose file and data together in a single directory for easier backups and migration. I weigh the tradeoffs of this approach and walk through migrating an Uptime Kuma container from a named volume to a bind mount by locating the volume’s files with docker inspect and copying them directly while the container is stopped, contrary to more complicated methods suggested elsewhere online...
I offer a short explainer on the difference between ENTRYPOINT and CMD at the end of a Dockerfile, with examples showing that command-line arguments replace CMD entirely but are appended to ENTRYPOINT. I also cover combining the two, and note that both execute at container launch, unlike RUN, which happens during the image build...
Docker environment variables set for a container aren’t available to scripts run by cron inside it, even when cron runs as the same user. After confirming the cause and finding no clean fix, I settle on a workaround: saving the needed variable to a file in the entry point script and reading it back from the cron job...
How to get a phone notification whenever someone SSHs into a VPS, using a PAM hook and the Ntfy push notification service. I explain why SSH access is a risk worth monitoring on otherwise locked-down servers, then walk through editing the sshd PAM config, creating a script that sends a curl request to an Ntfy topic on login, and restarting sshd to test it...
Upgrading Forgejo from 1.21 to 7.0.0 takes more than the usual pull on an existing tag, because the container tag changes with the minor version. After explaining how container image tags work, I update my docker-compose.yml to pin Forgejo 7.0 and run the usual backup, down, pull, up, and test steps...
A first-draft Dockerfile for my Node project, and how I keep the resulting image tidy by inspecting its contents with an interactive bash shell and adding entries to .dockerignore. I also explain my preference for copying everything over explicitly listing files, citing debugging time and security...
I take a look at using NGINX as a reverse proxy in front of self-hosted services, and at NGINX Proxy Manager, a project that adds a web GUI to the process and simplifies obtaining Let’s Encrypt certificates. I cover setup via Docker, along with a brief comparison of alternatives such as HAProxy, Caddy, and Traefik...
I describe my standard, reproducible homelab setup: a Proxmox-based system of three machines with nightly backups to a Synology NAS, where most apps run as Docker containers managed by docker compose inside LXCs cloned from a prepared template. An upgrade of Forgejo from 1.21.1 to 1.21.8 demonstrates how quick and low-risk maintenance has become under this arrangement, involving a container backup, an image pull, a restart, and some brief testing...
I walk through turning my small Node.js Markdown server into a Docker image, explaining the Dockerfile line by line, building the image locally, and pushing it to Docker Hub. I finish with a sample docker-compose file so self-hosters can deploy the app with a single command...
I walk through hosting a private Docker registry on my homelab network using Docker’s official registry image, motivated by the free tier’s single-repository limit and Docker Hub’s pull/push rate limits. I cover setting up the registry with docker-compose, building a small test image, and pushing it to the registry without TLS by adding an insecure-registries exception to daemon.json. A third machine pulls and runs the image to confirm it all works...