---
title: "Moving Out Of Ubuntu 10.04"
url: https://deallocatedobjects.com/posts/moving-out-of-ubuntu-10-04
author: Jake Marsh
published: 2026-10-23
updated: 2026-10-23
topics: ["This Blog"]
description: "This blog has been sitting on the same Linode server for a very long time. Deallocated Objects started on WordPress in 2010, moved to a little Rails app I wrote around 2014, and that Rails app has been quietly running ever since on a box I’ve been paying for since 2012. This month I finally moved…"
---

# Moving Out Of Ubuntu 10.04

By Jake Marsh, October 23, 2026. HTML: https://deallocatedobjects.com/posts/moving-out-of-ubuntu-10-04

This blog has been sitting on the same Linode server for a very long time. Deallocated Objects started on WordPress in 2010, moved to a little Rails app I wrote around 2014, and that Rails app has been quietly running ever since on a box I've been paying for since 2012.

This month I finally moved it. It turned into a fun little archaeology project, so here's how it went.

### What Was On The Box

Opening it up was a trip:

- **Ubuntu 10.04 LTS**, 32-bit. Ubuntu stopped supporting 10.04 in 2015.
- nginx 1.2.7, Postgres 8.4, MySQL 5.1, Redis, and the Rails 4 app running on unicorn with sidekiq next to it.
- `sshd` only offered `ssh-rsa` and `ssh-dss` host keys, which modern OpenSSH won't use without some coaxing.
- The HTTPS certificate expired on **November 28th, 2016**. It only spoke TLSv1 anyway. So HTTPS on this blog has been broken for about ten years, and I never noticed.
- About 6 GB of the 9 GB on disk was Rails logs.

It also had the backend for the original Conditions iOS app from 2013, a Huginn install from 2014, and an even older blog app with exactly one article in it. All for $29 a month.

### Getting The Data Out

I didn't have a working password or SSH key for the box anymore. Linode has a rescue mode that boots a small Linux distro called Finnix, so I booted into that, mounted the old disk read-only, and ran Postgres 8.4 out of a scratch overlay so nothing would ever write to the original disk. From there, `pg_dump` for every database, a test restore to make sure the dumps were good, and a full copy of the filesystem.

The blog's database turned out to be tiny: 82 rows in the `posts` table (80 published, 2 drafts), plus 9 Safari push notification subscribers. Hi, nine people!

### Going Static

A blog that hasn't changed since 2015 doesn't need a Rails app, a database, and a background job queue to serve it. So I rebuilt it as a static site: a Bun and TypeScript script, about 300 lines with no runtime dependencies, that reads the posts and writes out plain HTML files. nginx serves those files and there's no app process at all.

The first surprise: the `html_content` column was empty for every post. The old app rendered markdown on every single request with Redcarpet and Pygments. Instead of trying to recreate Redcarpet's exact quirks, I crawled the live site before shutting anything down and kept each post's rendered HTML exactly as the old app produced it. Same output as before.

The second surprise: the order of posts on the index pages. The app sorted by `published_at`, and when two posts shared a date, Postgres broke the tie however it felt like that day. Re-sorting them would have shuffled a few posts around, so the index order and every post's previous/next links are frozen from the crawl.

### Every Old URL Still Works

This was the part I cared most about. People have linked to these posts for years, and I didn't want to break any of them:

- `/posts/<slug>` and `/posts/<id>` (the Rails app answered both; the id now 301s to the slug)
- `/posts/<slug>.json` and `/posts.json`
- `/feed`, `/feed.atom` and `/posts.atom`
- the sitemaps, `robots.txt` and `humans.txt`
- five URLs from the 2010 WordPress days, which still 301 to the right posts

Pagination was the one tricky bit for a static site, since the old URLs use a query string (`/posts?page=3`). nginx handles that by mapping the query to a pre-built file:

```nginx
location = /posts {
    if ($arg_page ~ "^[0-9]+$") { rewrite ^ /_pages/$arg_page.html last; }
    try_files /index.html =404;
}
```

To check all of it, I wrote a little verify script that requests every URL on the new site (358 of them) and compares the title and text of every page against the crawl of the old one. 358 URLs, 0 failures, 101 out of 101 pages matching.

Along the way I found a bug that had been live for years. The post ["7 Keys to Unlock an Apple Design Award"](https://deallocatedobjects.com/posts/7-keys-to-unlock-an-apple-design-award) was a 404 on the old site. The app decided whether a URL was an id or a slug by calling `to_i` on it, and in Ruby `"7-keys-to-unlock-an-apple-design-award".to_i` is `7`. So it went looking for post #7, which doesn't exist. It works now.

### Images

Posts referenced 94 image URLs. The ones in my own S3 buckets were all still there, so I downloaded them. A bunch of others were already dead, mostly old CloudApp (`f.cl.ly`) links. I pulled 22 of those back out of the Wayback Machine. In the end 70 images are self-hosted with the site now, and 19 CloudApp images are just gone. RIP.

### What Didn't Make It

Google Analytics (the old Universal Analytics kind, which Google shut down in 2023), Chartbeat, the Facebook SDK, the share buttons (the Hacker News button's host doesn't even exist anymore), jQuery, and the Safari push notification endpoints. Sorry, nine subscribers.

The FeedBurner feed link still works. Wild.

Now the whole site is a folder of files. Deploying is a build and an `rsync`, HTTPS works again for the first time since 2016, and the old Linode is deleted. I did most of this with Claude Code, which was great at the careful, boring parts, like diffing every page of the crawl against the rebuild.

It feels really good to have this place back. I've already started writing here again.

*Update: the site has picked up a few more features since this post. How it all works today is in the [colophon](https://deallocatedobjects.com/colophon).*
