Jan. 10, 2026
Remote-SSH
One of the things I’ve done a bit in Visual Studio Code is using it’s ability to work on a different machine over SSH. I have a couple of LXCs on a server set up for different languages - one for C++ and another for Rust. They are things I don’t work in often, and I didn’t want to set them up on my laptop, but thought I might want them again sometime in the future.
Jul. 28, 2025
Ghostty is a terminal application that I don’t really need (it’s listed features either already exist in the MacOS terminal, or seem so esoteric or marginal that I can’t imagine any real benefit from them in my normal use), but I wanted to be one of the cool kids, so I thought I’d give it a try.
After fiddling around with the themes for a bit I renamed it to ’term-ghosty.app’ so I’d remember to use it (ie when I pop up spotlight and type ’term’ it will come up) and got on with my day. Ten minutes later I’d run into a problem.
Jul. 7, 2025
I’ve been meaning to write this for a couple of weeks, so let’s get to it - things are moving to fast to reflect too long; which is it’s own risk.
In March, I wrote about how I was using AI in coding , which was Codeium (now Windsurf) in VS Code for completions, and ChatGPT and Claude online for architecture questions and code gen that was more than half a function.
Jun. 22, 2025
Web pages are mostly just a collection of HTML, CSS, and JavaScript, so if we had some way of adding some of these into a web page, perhaps from our browser we could add new behaviour to a web page, right?
Yes; users have long used tools like Greasemonkey (or similar userscript managers) to inject scripts into pages. Better still, modern browsers expose JavaScript APIs that let us interact directly with the browser itself. Enter: browser extensions.
May. 12, 2025
When you’ve made a change to your web-app, do you run it then click around the new bits to check it works? Good start, but instead of doing that yourself, do it in a faster, more comprehensive and automated way with an end-to-end (E2E) testing setup using Cypress . Here’s how.
E2E
End to End testing is testing your app as a user might - by clicking links, entering data, looking at the screen and checking everything is okay, but it’s scripted like a unit test and the results are checked with assertions. Like unit testing this allows you to build up a collection of comprehensive tests that easily detect for unexpected behaviours - not just in the results of functions in your app, but in the user experience of the app.
Apr. 28, 2025
A Node/Express app I’m working on has been sprouting routes so much that the server.js file has swollen to 800 lines - way past my 200-250 comfort zone, so it’s time to organise the routes into their own files. That seems like a good topic for a beginner blog post, so let’s dive in.
Imagine we’ve written this little Node/Express app.
import express from "express";
import {
dbCustomersGet,
dbCustomersGetById,
dbCustomersDelete,
dbOrdersGet,
dbOrdersGetById,
dbOrdersGetByCustomerId,
dbOrdersDelete,
} from "./db.js";
const app = express();
app.set("view engine", "ejs");
const port = 3002;
app.use(express.urlencoded({ extended: true }));
app.get("/", (req, res) => {
res.redirect("/customers");
});
app.get("/customers", (req, res) => {
const customers = dbCustomersGet();
res.render("customers", { customers });
});
app.get("/customers/:id", (req, res) => {
const customer = dbCustomersGetById(req.params.id);
const orders = dbOrdersGetByCustomerId(req.params.id);
res.render("customer", { customer, orders });
});
app.get("/customers/:id/delete", (req, res) => {
dbCustomersDelete(req.params.id);
res.redirect("/customers");
});
app.get("/orders", (req, res) => {
const orders = dbOrdersGet();
res.render("orders", { orders });
});
app.get("/orders/:id", (req, res) => {
const order = dbOrdersGetById(req.params.id);
const customer = dbCustomersGetById(order.customerId);
res.render("order", { order, customer });
});
app.get("/orders/:id/delete", (req, res) => {
dbOrdersDelete(req.params.id);
res.redirect("/orders");
});
app.listen(port, () => {
console.log(`Listening on http://127.0.0.1:${port}`);
});
Although concocted, this would seem familiar to anyone who’s built a CRUD business app.
Apr. 14, 2025
I’ve been whipping up a little mock-database unit that has a few access functions but actually stores the data as arrays for a demo project for a post I’m writing. In the process I wrote this gem:
export function dbOrdersAdd(order) {
const orderCopy = { ...order };
// since id is a stringified number, finding the max is a bit of a mess
const maxId = orders.reduce((max, o) => Math.max(max, parseInt(o.id)), 0);
orderCopy.id = String(maxId + 1);
orders.push(orderCopy);
return { ...orderCopy };
}
In the comment I’m claiming the code is a bit of a mess (and from a readability point that’s true) but actually I love the elegance of using the reduce() method here.
Dec. 30, 2024
I love the convenience of a hosted blog on wordpress.com, but one of the justifications for my ‘investment’ in homelab hardware and learning time was that I’d reduce my spend on hosted platforms by self-hosting them. I’ve already quit Evernote and dropped back to the free plan on Dropbox by building systems to replace them for less money and more data sovereignty. And now, the recent Wordpress drama has made me uneasy about Matt having control of domains I’ve got registered with wordpress.
Oct. 28, 2024
If you’re interested in how generative AI works, check out Ishan Anand ’s Youtube series “Spreadsheets are all you need ”. He steps through the basics using an Excel spreadsheet that encompasses most of GPT-2. Just doing that is an impressive (and hilarious) feat, but he also has a knack for teaching, so you’ll come away with a good understanding of AI and how some of it’s limitations manifest.
Ishan is selling a course, which I guess these are the first three lessons of, and I got a lot out of them. It’s also possible to download the spreadsheet he uses in the course to play with.
Aug. 19, 2024

Pretty much every serious web app needs to include a way for users to log in securely and to be served their content. Since there’s a lot of complexity in this, it’s highly advisable to use good libraries to support this. In a future post we’re going to use those libraries, but first I want to explain what’s happening at the lower level and tease out some of the concepts as we build a secure system from the ground up.
Jul. 29, 2024
Now Ollama has made it simple enough for anyone who can use a terminal to run large language models locally, naturally I’ve gone overboard downloading too many to play with. I’m increasingly feeling they definitely have a place in the devops/coding arsenal of tools, but which model is best?
If you go on HuggingFace to look at a new model you’re interested, they often have great comparisons like this.

Jul. 22, 2024
There are two entries we often have at the end of a dockerfile (which is the file that tells Docker how an image is to be built).
They are similar in that when the container is launched from an image, these commands will be executed. For example, both of the dockerfiles below will print “Hello World” when run.
doc-entry:
FROM debian:stable-slim
ENTRYPOINT ["echo", "Hello World from ENTRYPOINT"]
doc-cmd:
FROM debian:stable-slim
CMD ["echo", "Hello World"]

Jul. 1, 2024

This post looks at the context for some of my thinking about AI for supporting software development, and where I’ve landed on it for the time being.
The landscape
I briefly wrote about ChatGPT’s coding ability at the end of 2022. The wide availability of this tool marked the beginning of what I think can fairly be described as a revolution. The controversies that have crystalised since have not dampened my amazement of this step forward in what compute can do, especially around natural language processing.
Apr. 22, 2024
I’ve been running NGINX Proxy Manager (NPM) in my homelab for a bit, and I’ve been meaning to clean up the VPS that runs most of my websites and public facing servers, so I’m considering running NGINX Proxy Manager on that VPS. While NGINX Proxy Manager wraps up the configs in a beautiful GUI, in the process you lose some of NGINXs capabilities. In particular there’s no GUI way to serve static virtual hosts from NGINX Proxy Manager.
Mar. 31, 2024

When I wrote the install instructions for mdserver (little Markdown server Node app) on it’s github page it was something like:
- Have node.js installed and working
- Clone the repo
- Start with
npm start
Which is great if you know how to do those things (they are bread and butter to a web dev) but not if you’re a self-hoster who just wants a web server that converts markdown to HTML on the fly. For any situation where you just want to use the app, what you probably want is a Docker image of the app.
Mar. 25, 2024

The Docker Personal (ie free tier) plan currently allows one private repository, but even if you want to pay for the next level where you can have unlimited repositories, you may still want to host your own private registry - it’s going to be quicker inside your network, and you won’t run up against Docker’s pull/push limits if you are hammering it with your CI/CD system.
Mar. 18, 2024
I had a number of domains all running on one host when I first set them up with certbot. One started to be serious, so I moved it to another host and ran certbot there. That all worked perfectly, but of course, the old domain is still part of the original certificate, so when I went to renew it, it came up with some errors.
Here’s a few commands that are going to help navigate this situation if you’ve found yourself in the same spot:
Feb. 16, 2024

Since I’m using Tailscale to painlessly manage all my networking on the homeserver here and my remotes, I’ve had the luxury of being a bit casual about the security of my internal apps and self hosted dev tools. I’m currently iterating on a web app that requires public access, and is therefore up on a VPS and exposed to all the evils of the open internet.
I am in no way a security expert, but here’s a few of the (reasonably simple) steps I’ve taken to secure my node app.
Feb. 9, 2024
When you are learning app development, you can create all sorts of apps that work for you, but for any serious app, it’s going to need to authenticate users and persist sessions across visits. So much so, that as a professional developer, you’ll probably build that out first - it becomes a sort of boiler plate you always drop in.
In this post, focusing on the server side, using node, express, and particularly express-session, I’ll try and build up from nothing to a reasonable usable user login system explaining the increasing complexity and reasons for it. To follow along you’ll need basic familiarity with node and express.
Feb. 2, 2024

I’ve been aware since I set up Uptime Kuma for my monitoring, that having an instance on my local network monitoring my VPS websites wasn’t ideal. The main reason being that the flakiest part of my infrastructure is my 4G home internet, so if that goes down I have no website monitoring, and even if I did, the notifications couldn’t get out.
Of course, it would also be a simple matter to run an instance on the VPS that I host the sites on, but that has a similar problem in that if the VPS goes down, so does my monitoring of the VPS. What I really need is a third, independent space to run an instance.