pstmrtm-001: standing up pstmrtm

· 4 min read
pstmrtm-001: standing up pstmrtm

Ghost on Ubuntu 24.04 - postmortem report


this report will document the process of getting pstmrtm up on its feet, a ghost-powered site running on a brand new ubuntu 24.04 image. what should have been a simple installation process quickly devolved into a series of mishaps involving node version conflicts, database permissions, routing errors, and assumptions at the theme level about how ghost is rendering content.
this postmortem report will walk us through what broke, why, and what changed.

context & environment
the aim was to launch a personal project and portfolio website using ghost as a cms, which would be hosted on a vps and would be accessible publicly with a custom domain.

environment details:

  • vps running ubuntu 24.04 lts
  • ghost cli for installation and management
  • node.js (initially mismatched, later corrected)
  • mysql as the underlying database
  • nginx as reverse proxy server
  • a custom ghost theme with opinionated routing and templates

the deployment was done directly on the server using ssh, with changes done at both the system and application levels.

what went wrong
what should have been a simple ghost install became a domino effect of problems at various levels of the stack. while none of the problems were disastrous in and of themselves, they combined to make for a confusing and frustrating installation process.

1. node.js version mismatch
ghost needs a very specific version of node. the system originally had an lts version of node installed that seemed “reasonable” but was not compatible with the version of ghost that was being installed. this led to the installation failing prematurely, even though node seemed up to date.

2. database setup and permissions friction
mysql was installed, but the default configuration (existence of anonymous users, ambiguous authentication expectations, assumptions about root access) introduced uncertainty during the ghost database setup phase. ghost requires a specific mysql user with database permissions on a dedicated database.

3. ghost cli safety constraints
ghost cli refuses to run as root, which is the right thing to do, but this added some friction to the initial installation process. switching between users and re-running commands caused some problems with partial installs and cleanup.

4. routing assumptions inside the theme
the theme of choice relied heavily on a custom routes.yaml file, the use of collections, and opinionated filters. instead of modifying the routes file in the theme, changes should have been made in ghost’s canonical routes.yaml file found in content/settings.

5. content vs template confusion
custom templates (reports, about, contact) were rendered properly, but some pages were “empty” because the templates were not explicitly rendering {{content}}, giving the impression that ghost was not paying attention to changes made in the admin-panel.

effect
the issues, while not causing any loss of data or down time, caused a substantial increase in the setup time and cognitive load. even simple tasks such as installing ghost, viewing posts, or editing a page from the admin panel required trial and error to ensure that the problem was in the server, ghost, theme, or routing layer.

the key effects were:

  • installation time escalated from minutes to hours
  • repeated false assumptions about what “was already working”
  • difficulty validating changes due to silent failures
  • loss of confidence in whether ghost admin edits were being respected
  • multiple rebuilds of routes and templates to isolate behavior

root cause analysis
there was no single failure. the problems arose from a series of small mismatches that piled up on top of each other.

runtime mismatch
ghost required a newer version of node than what was originally installed on the server. even after the upgrade of node, the ghost-cli showed the old version, giving a false impression that the environment was still broken.

database assumptions
mysql was installed, but ghost requires a separate database and user with particular permissions. until these were explicitly created and granted, ghost could not complete its setup.

filesystem permissions
some theme files and configuration files were owned by different users (ghost vs ghostadmin), which prevented edits from applying cleanly and caused permission errors when writing routes.

routes.yaml confusion
the edits were first done on the theme’s routes.yaml, not the active content/settings/routes.yaml. this made ghost ignore the changes silently, as if the routing was broken when the wrong file was being modified.

theme-level filtering
the theme applied tag-based filters that excluded some posts from collections by default. this resulted in the disappearance of reports from the homepage and made the reports page appear empty even when there was content.

template expectations
custom templates (such as reports.hbs, page-about.hbs, and page-contact.hbs) did not consider feature images or content blocks on pages, resulting in “empty” pages despite valid content in the ghost admin.

individually, each problem was trivial. collectively, they caused cascading failures that were difficult to analyze because ghost does not expose clear errors for misrouted or mis-templated content.

resolution & lessons learned
it was not a single failure that led to the incident but several misalignments in the stack.

initial troubleshooting was performed on individual symptoms—ghost failing to start, routes resulting in empty pages, templates ignoring admin content—but these symptoms were only the downstream effects of more fundamental structural problems. the fundamental error was in assuming that ghost would somehow compensate for the lack of consistent configuration at the levels of runtime, database, routing, and theme.

resolution required considering the deployment as a system, and not an application. the node runtime environment was locked to a ghost-supported version, mysql users and permissions were rebuilt from scratch, and routing logic was moved from theme files to ghost’s system-managed routes.yaml. custom templates were refactored to use ghost’s collection and page contexts instead of hard-coded assumptions.

there were several instances where failures resulted in no explicit errors. the ghost often returned successful responses while silently skipping routes that were misconfigured or rendering empty contexts.

the main takeaway here is that the absence of errors does not mean that things are correct.