Resurrecting a Dead Blog: From Ruby 2.3 to a Working Site in One Session

| Comments

ENPT

This blog had been silent since 2017.

Not just the content. The entire infrastructure underneath was dead. Ruby 2.3, end-of-life since March 2019. Jekyll 2.x, unsupported. Compass for CSS compilation, a project that stopped in 2016. The GitLab CI pipeline was failing silently on every push.

Nobody noticed because nobody was pushing.


The Autopsy

The CI used ruby:2.3 from Docker Hub. That image still exists, frozen in time, based on Debian Stretch.

Debian Stretch’s package repositories have been archived. Every apt-get update returns 404. The image cannot install anything. Not even locales.

1
2
3
docker run --rm -it -v $(pwd):/blog ruby:2.3 bash
# apt-get update
# => 404 Not Found on every mirror

That was the end of Ruby 2.3.


The Fix: Minimum Viable Migration

The goal was not to modernize everything. The goal was to make it build again.

ruby:2.7 on Debian Bullseye. Repositories work. The image is stable.

Three things broke when moving from 2.3 to 2.7.

Bundler. The default bundler now requires Ruby 3.2+. Pinning to 2.4.22 solves it:

1
- gem install bundler -v 2.4.22

FFI. The ffi gem versions above 1.17 require Ruby 3.0+. Pinning to ~> 1.16.3 in the Gemfile prevents bundler from pulling an incompatible version.

Pygments. The pygments.rb gem (version 0.6.3) uses a Python IPC protocol to communicate with the Pygments syntax highlighter. That protocol is broken with Python 3.

The fix: replace it entirely with Rouge, a syntax highlighter written in pure Ruby. No Python dependency. No IPC. The plugin pygments_code.rb was rewritten to call Rouge’s API:

1
2
3
4
5
require 'rouge'

lexer = Rouge::Lexer.find_fancy(lang, code) || Rouge::Lexers::PlainText
formatter = Rouge::Formatters::HTML.new
highlighted = formatter.format(lexer.lex(code))

No posts needed changes. The same codeblock Liquid tag works exactly as before.


Local Development: Easier Than Expected

The assumption was that Ruby 2.7 would only work inside Docker due to native extension compilation (ffi, rdiscount, RedCloth).

Wrong.

With mise on Arch Linux:

1
2
3
4
mise install ruby@2.7.8
gem install bundler -v 2.4.22
bundle install
bundle exec rake generate

Every native gem compiled without issues. bundle exec rake preview serves the site at localhost:4000. No Docker required for local development.


Cleaning Up the Dead Weight

With the build working, the next step was removing everything that had died while the blog was sleeping.

Dead integrations removed: - Google+ sidebar widget and +1 button — service shut down April 2019 - Google Analytics (Universal Analytics) — retired July 2024, collecting zero data - Facebook Like button — privacy-invasive, removed - Twitter sidebar widget — removed from default sidebar

Dead code identified but left alone: - Flash/SWFObject in octopress.js — inert, no browsers use Flash - Modernizr 2.0 — feature detection for browsers that no longer exist - jQuery 1.9.1 — still used by the nav, not worth removing without a rewrite

Posts hidden: five low-value posts (test posts, reposts, dated content) set to published: false. Not deleted — just invisible.


Frontend: Small Changes, Visible Impact

The Octopress 2 default theme is from 2012. A full redesign was out of scope. But a few CSS-only changes made a noticeable difference.

Typography. The font stack was broken — Sass was treating the entire heading font declaration as a single quoted string. Browsers never matched it. Fixed to use proper PT Sans / PT Serif stacks with correct Google Fonts loading over HTTPS.

Text width. The content column had no max-width constraint. On wide screens, text lines stretched far beyond comfortable reading length. A single max-width: 38em on .entry-content fixed it.

Background. The original tiling PNG texture was replaced with a solid color. The nav bar now sits on a clean dark background instead of a noisy image.

Blockquotes. Added subtle background color and proper padding for a more modern feel.


Open Graph and Social Sharing

The site had no Open Graph or Twitter Card meta tags. Every shared link appeared as a plain URL with no preview.

Added to the <head> template: - og:title, og:description, og:image, og:type, og:url - twitter:card, twitter:title, twitter:description, twitter:image

These are now populated automatically from each post’s front matter. Posts with an image field get summary_large_image cards. Posts without get a basic summary card.


What Remains

The stack is still far from modern. Jekyll 2 and Octopress are dead projects. Compass generates vendor prefixes that no browser has needed in years. The JavaScript loads jQuery 1.9.1 and Modernizr 2.0 on every page.

The Compass dependency is the main blocker for a Jekyll 4 migration. Every Sass file uses Compass-specific mixins and asset helpers that do not exist in standard Sass. Replacing them is a multi-day effort.

But the site builds. The CI passes. Posts render. The content is readable.

Sometimes resurrecting something old is faster than rewriting from scratch.

The next step is deciding whether to invest in a full Jekyll 4 migration or accept that this Octopress shell, despite its age, still works well enough to publish what matters.

For now, it publishes. That is enough.

Comments