blog.iankulin.com

Posts

Perils of Benchmarking

After choosing the tiny BusyBox httpd server for containerised websites, I encountered third-party benchmarks suggesting NGINX was dramatically faster. Running my own A/B tests with Apache’s ab tool initially gave misleading results, which turned out to be NGINX Proxy Manager serving cached responses. With cache-busting requests, BusyBox and NGINX performed similarly over the network, so I am sticking with the 1.35MB BusyBox containers over the roughly 49MB NGINX-alpine images...

Moving a domain from Wordpress

A step-by-step guide to moving my domain registration from WordPress.com to Porkbun, motivated by my unease over recent WordPress drama. I cover the transfer process—including a required 60-day wait, disabling the transfer lock, and a gotcha where private registration had to be turned off before the authorization code would work—plus copying DNS records and switching nameservers. In a closing update, I note that WordPress later blocked external domain pointing, pushing me toward self-hosting...

Updating a deployment on fly.io

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...

NGINX proxy manager - setting headers to use basic auth in your apps

Using NGINX basic auth as a lightweight alternative to building authentication for my side projects. For a standard NGINX setup, the authenticated username is passed to a Node app in an X-Username header and checked against a user list for access rights. I then cover replicating this in NGINX Proxy Manager, using access lists and the locations tab to set the header...

Controlling Docker container startup order

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...

Fixing TLS for wget in BusyBox

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...

Fancier Website in a Docker Container

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...

Website in a Docker Container

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...

Using the GitHub Container Registry

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...

Clear explanation of how transformer AI works

I recommend Ishan Anand’s YouTube series Spreadsheets are all you need, which teaches how generative AI works using an Excel spreadsheet implementing most of GPT-2. I highlight Anand’s teaching ability, note that the featured lessons are the first three of a paid course, and link to a downloadable version of the spreadsheet...

npm ERR! Exit handler never called!

My routine npm update, triggered by a GitHub security advisory, runs into the cryptic “Exit handler never called!” error. Alongside a refresher on what package-lock.json and npm update do, my fix is to confirm the lock file is at fault using npm install –no-package-lock, then delete it and regenerate it with a fresh npm install, followed by retesting and rebuilding any artifacts...

Code reuse by publishing to NPM

I walk through publishing my own JavaScript utility to npm so it can be installed and updated like any other package, rather than copying source files between Node projects. I cover creating an npm account, scoping the package name to my username, writing and publishing the code with npm publish, and installing it in another project, using a simple is-even example...

rsync between Synology NAS

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...

Containerised NGINX Proxy Manager & the 502 error

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...

Uploading files to a web app with Node

A beginner-friendly walkthrough of enabling file uploads in a Node/Express web app using the multer middleware. I cover setting up a basic Express server, configuring multer’s disk storage, and submitting files via an HTML form with multipart/form-data encoding, before briefly noting production concerns like sanitizing filenames and restricting file size and type...

Authentication basics for Node apps

I walk through building user authentication in an Express.js app from the ground up, starting with how HTTP requests, cookies, and sessions work before moving to session IDs and the express-session library. I then cover a login flow with bcrypt-hashed passwords and file-based session persistence, laying groundwork for a future post that will use Passport.js...

Moving from Docker volumes to bind mounts

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...

LLM coding question comparison using Ollama

Having downloaded a pile of large language models to run locally with Ollama on my M1 MacBook, I compare how several of them (codeqwen, deepseek-coder, phi3, dolphin-mistral, and llama3 at different quantisation levels) answer a Docker question about CMD versus ENTRYPOINT. I time each response and judge it subjectively, with caveats that the test is unscientific and benchmark charts usually reflect full-size models on expensive hardware. My writeup includes a layman’s explanation of quantisation, brief notes on each model’s character, and I conclude that llama3 and most others gave usable answers while phi3 fell short...

dockerfile - CMD vs ENTRYPOINT

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...

User environment variables are not available in cron

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...