blog.iankulin.com

blog.iankulin.com

Outputting to the console, in Docker, from a cron job

How to get console output from a cron job running inside a Docker container. I cover running cron in the foreground so the container doesn’t exit, and redirecting job output to /proc/1/fd/1 so it appears in the container’s stdout, since cron normally sends output to mail. I mention common gotchas like user permissions and file paths, with an example project on GitHub...

Using LLMs for coding

I survey the rise of AI coding tools, from ChatGPT and GitHub Copilot to open source ecosystems, alongside skeptic concerns about training data, code quality, model collapse, and code exfiltration. I explain why these assistants suit my situation as an experienced developer new to modern languages, and trace my journey from paid Copilot to the free Codium and finally to running local models via Ollama and the Continue VSCode extension to keep client code private...

SSH login notification

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 to Forgejo 7.0.1

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

Peek inside a Docker image

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

Virtual Hosts on "Static Web Server"

My VPS runs NGINX Proxy Manager, which offers no way to serve static virtual hosts, so I walk through running Static Web Server—a lightweight Rust-based web server—in Docker alongside it. I cover the directory layout, a docker-compose setup, and the config.toml virtual host entries, with the result verified using Tailscale addresses as stand-in hostnames...

NGINX Proxy Manager

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

Due Diligence on a Docker Image

When setting up self-hosted LimeSurvey, I find no official Docker image, so I work through how to decide which third-party container image to trust. I lay out rough criteria, such as known maintainers, popularity, update frequency, and inspectable build files, then compare several LimeSurvey images on Docker Hub and trace their provenance before settling on one...

My Web App Update Process

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

Deploying a Node app in Docker

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

Hosting Your Own Docker Registry

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

Certbot - removing a domain

After I moved a domain to a new host, the original certbot certificate on the old host still included it, causing renewal errors. I explain that domains can’t be deleted from a certificate directly; instead you renew the certificate specifying only the domains you want to keep, and certbot warns about the ones being dropped. I include the commands for listing certificates and their domains, and for renewing with a reduced domain set...

Quick & Dirty auth with nginx & Node

I walk through protecting a Node/Express app with nginx basic auth, aimed at simple utilities on public servers: firewall off the app’s port so only nginx can reach it, configure nginx to proxy requests and require htpasswd credentials, and pass the authenticated username to the app via a request header. I also cover the approach’s limitations, such as plaintext passwords without SSL, no logout mechanism, and no brute-force protection...

Beginning Node App Security

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

User Sessions & Cookies in Node

In this tutorial, I build a user login and session management system from scratch in Node and Express, starting with a simple cookie-based session counter and progressively adding file-based persistence, user accounts, logout, and password authentication using bcrypt. I also cover input sanitisation, secure cookies, and enforcing HTTPS behind an NGINX proxy. The tutorial is aimed at readers with basic familiarity with Node and Express...

Web Development Overview

In this short post, I link to Brad Traversy’s Web Development In 2024 – A Practical Guide YouTube video, describing it as a comprehensive, beginner-friendly overview of web development that he publishes each year...

Fly.io, Uptime Kuma & scraping a status page

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

How to Have Cooler File Icons in VS Code

A short walkthrough of fixing the default VS Code file icons in the explorer view. I explain how to find the File Icon Theme option via the command palette, why a vanilla install requires installing additional themes from the marketplace, and how I settled on the popular VS Code Icons theme...

Getting Your Vite React App to Work on Github Pages

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

React Expense Tracker App

This is a write-up of an exercise from Mosh’s React 18 course: building a small expense-tracking app in React and TypeScript, with Zod used for form validation. I cover component breakdown decisions, persisting expenses to local storage, tradeoffs in TypeScript type definitions, and refactoring repeated table markup into reusable components...