Skip to content
STACK IT FAST

Ghost

Curated OSSClassicContent Platform20+ people

Audited from github.com/TryGhost/Ghost

Ghost is an open-source, self-hostable Node.js publishing platform for blogs, newsletters and paid memberships, built as a pnpm/Nx monorepo containing a core server, a React admin app migrating off Ember, and a set of independently built embeddable widgets.

Language
TypeScript
Database
SQLite
Hosting
Docker
License
MIT
Running for
9 years
Team
20+ people

Why this architecture

Ghost runs its core server, a React admin app that is replacing a legacy Ember admin in place, and every public-facing widget as separately Vite-built packages inside one pnpm/Nx monorepo, so the publishing surface, the editor and each embeddable script can evolve and ship on their own schedule.

Tech stack by layer

13 technologies · audited Sep 17, 2026
Frontend & UI
  • ReactNew admin dashboard (apps/admin) built with Vite, TanStack Query and Radix UI primitives, replacing the Ember admin in place.
  • EmberLegacy ghost-admin client (apps/ember-admin) still ships from the monorepo alongside the newer React admin during the migration.
  • ViteBundler for the new admin app and every embeddable widget (portal, comments-ui, sodo-search, signup-form, announcement-bar, activitypub).
  • PreactPowers admin-toolbar, the lightweight staff toolbar injected into published site pages.
  • HandlebarsTemplating language for server-rendered themes; the default Casper theme ships under ghost/core content/themes/casper.
Backend & APIs
  • Node.jsghost/core is the Node.js application server; the dev Docker image pins Node 22.
  • Ghost API frameworkInternal @tryghost/api-framework and @tryghost/admin-api-schema packages provide shared REST request handling and JSON-schema validation for ghost/core.
Data & Persistence
  • SQLiteSelectable local/dev persistence backend (better-sqlite3) via a dedicated dev:sqlite compose profile.
  • ClickHouseStorage engine underneath the local Tinybird analytics stack used to develop and test analytics events.
  • TinybirdReal-time analytics platform for site/traffic analytics; e2e tests sync fixture state against a local Tinybird instance.
Infrastructure & Deploy
  • DockerDev container (docker/ghost-dev), a fake-mailgun stub for local email testing, and the flattened Tinybird/ClickHouse local image.
  • pnpmMonorepo package manager with a strict dependency catalog (catalogMode: strict) shared across every workspace.
Tooling, Testing & Ops
  • NxTask orchestrator running cached build/lint/test targets across every ghost/*, apps/*, packages/* and koenig/* workspace.
  • VitestPrimary test runner for ghost/core and most apps/packages (test:watch, test:unit scripts).

Ghost architecture diagram

Open SVG
Ghost architecture diagramReaders → Theme rendering (pages); Readers → ghost/core (portal · comments); Admin → ghost/core (Admin API); Theme rendering → ghost/core (content); ghost/core → Database (queries); ghost/core → Tinybird (events); ghost/core → Email (newsletters)CLIENTSSERVICESDATA & STORAGEEXTERNALReadersthemes · widgetsAdminReact (+ legacy Ember)ghost/coreNode.js · Admin & Content APITheme renderingHandlebarsDatabaseSQL (SQLite in dev)Tinybirdanalytics · ClickHouseEmailMailguncontentpagesportal · commentsAdmin APIquerieseventsnewsletters
How the main components of Ghost connect, drawn from the audited repository.
Diagram as text
  • Readers (themes · widgets) → Theme rendering (Handlebars): pages
  • Readers (themes · widgets) → ghost/core (Node.js · Admin & Content API): portal · comments
  • Admin (React (+ legacy Ember)) → ghost/core (Node.js · Admin & Content API): Admin API
  • Theme rendering (Handlebars) → ghost/core (Node.js · Admin & Content API): content
  • ghost/core (Node.js · Admin & Content API) → Database (SQL (SQLite in dev)): queries
  • ghost/core (Node.js · Admin & Content API) → Tinybird (analytics · ClickHouse): events
  • ghost/core (Node.js · Admin & Content API) → Email (Mailgun): newsletters

Key architectural decisions

7 decisions
  1. 01

    React admin is replacing Ember admin in place, not on a separate branch

    apps/admin (name @tryghost/admin, Vite + React + TanStack Query + Radix UI) and apps/ember-admin (name ghost-admin, Ember.js, version 6.64.1-rc.0) both live in the same monorepo and ship together, so the admin UI migrates screen by screen instead of as a big-bang rewrite.

  2. 02

    Every embeddable public widget is its own Vite-built UMD package

    portal, comments-ui, sodo-search, signup-form, announcement-bar, admin-toolbar and activitypub each declare a umd/ output and a shared build config from configs/vite-public-app/package.json, so a published Ghost site loads only the small standalone scripts it actually uses instead of one monolithic admin bundle.

  3. 03

    Site analytics run on Tinybird/ClickHouse, not an in-house pipeline

    docker/tinybird-local-slim/Dockerfile flattens a real Tinybird-local image (CLICKHOUSE_VERSION env var, MinIO-backed storage) for local and CI use, docker/tb-cli/Dockerfile installs the Tinybird CLI itself, and e2e/package.json exposes a tinybird:sync script to keep local Tinybird state aligned with test fixtures.

  4. 04

    pnpm plus Nx with a locked dependency catalog governs the whole monorepo

    pnpm-workspace.yaml sets catalogMode: strict, blockExoticSubdeps: true and a minimumReleaseAge of 3 days for new dependency versions, while nx.json defines cached build/lint/test targets that run across every workspace listed in pnpm-workspace.yaml (ghost/*, apps/*, packages/**, koenig/*, configs/*).

  5. 05

    ActivityPub support ships as its own app, not bolted onto ghost/core

    apps/activitypub is a separate @tryghost/activitypub package with its own Vite build and an ./api export consumed by apps/admin, keeping Fediverse protocol code out of the main ghost/core server package.

  6. 06

    The editor is a Lexical-based rewrite, kept compatible with the older Mobiledoc format

    apps/admin depends on @tryghost/koenig-lexical for its rich-text editor, while koenig/kg-default-cards still ships "Mobiledoc card definitions for Ghost's editor" (version 10.3.6) so posts authored under the previous Mobiledoc-based editor keep rendering.

  7. 07

    Local dev containerizes optional external dependencies, including email

    docker/fake-mailgun/Dockerfile provides a stand-in for Mailgun so pnpm dev:fake-mailgun works without real credentials, and root package.json defines separate compose-profile scripts (dev:sqlite, dev:mailgun, dev:analytics, dev:storage, dev:stripe) that opt individual optional services in or out of the dev stack.

How Ghost is built

How Ghost is structured

Ghost is a single pnpm/Nx monorepo (ghost-monorepo in the root package.json). pnpm-workspace.yaml defines the workspace globs: ghost/* for the core server, apps/* for every UI application, packages/** for shared internal libraries, koenig/* for the rich-text editor's card system, and configs/* for shared tool configuration (ESLint, TypeScript, Vite). nx.json layers cached, dependency-aware build/lint/test targets on top of that structure so a change in a shared package only re-triggers builds for the apps that depend on it. The server package itself, ghost/core (name ghost, currently 6.65.0-rc.0), is the actual publishing engine; everything under apps/ is a separately versioned, separately built client of it.

Frontend

Two admin clients coexist during an active migration: apps/ember-admin (package name ghost-admin) is the original Ember.js dashboard, still built with ember build and ember serve; apps/admin (@tryghost/admin) is the newer React application, built with Vite and using TanStack Query for data fetching, Radix UI primitives (@radix-ui/react-focus-guards, @radix-ui/react-focus-scope) for accessible UI behavior, CodeMirror (@uiw/react-codemirror) for code fields, and Sentry (@sentry/react) for error reporting. Beyond the admin apps, every public-facing embed is its own small Vite-built package sharing a common config from configs/vite-public-app: apps/portal (membership/subscription UI), apps/comments-ui, apps/sodo-search (search, on React 17 via a dependency catalog entry), apps/signup-form, apps/announcement-bar, apps/activitypub (Fediverse UI), and apps/admin-toolbar, which uses Preact instead of React for a smaller footprint since it is injected into every published page. The rich-text editor itself is @tryghost/koenig-lexical, consumed by apps/admin; the koenig/kg-default-cards package continues to ship "Mobiledoc card definitions for Ghost's editor" so content authored before the Lexical-based editor still renders. Published sites themselves are server-rendered from Handlebars theme templates — ghost/core/package.json's files array explicitly packages content/themes/casper/, the bundled default theme — separate from both admin frontends.

Backend & APIs

ghost/core is the Node.js server; its Docker dev image (docker/ghost-dev/Dockerfile) pins NODE_VERSION=22.23.1 on a node:$NODE_VERSION-bookworm-slim base. Shared REST plumbing lives in dedicated packages rather than inline in ghost/core: packages/api-framework (@tryghost/api-framework) provides common request/response handling used across API versions, and packages/admin-api-schema (@tryghost/admin-api-schema) holds the JSON schemas used to validate Admin API requests. ghost/core's own package.json exposes build/test scripts like build:tsc, build:assets:js/:css/:cards and a Vitest-based test:unit, indicating the server package is itself TypeScript-compiled rather than run directly from source.

Data & persistence

For local development, pnpm-workspace.yaml explicitly allows the native build for better-sqlite3, and the root package.json exposes a dev:sqlite script that starts the stack with compose.dev.sqlite.yaml, giving Ghost a SQLite-backed persistence path without any external database service. Analytics data is handled through a separate pipeline: docker/tinybird-local-slim/Dockerfile flattens a real Tinybird-local image for local/CI use, setting CLICKHOUSE_VERSION=25.3.7.194 and MinIO credentials for its object storage layer, while docker/tb-cli/Dockerfile installs the Tinybird CLI (pinned to 4.6.13) used to manage that analytics backend. e2e/package.json's tinybird:sync script keeps a local Tinybird instance's schema and fixture state in sync for end-to-end tests.

Build, test & deploy

CI is entirely GitHub Actions: ci.yml is the main pipeline, with dedicated workflows for release.yml, publish-packages.yml, publish-tb-cli.yml, publish-tinybird-local-slim.yml, devcontainer-build.yml, migration-review.yml and several i18n/translation review workflows (i18n-review-checks.yml, translation-review.yml, stale-i18n.yml). The root package.json centralizes cross-cutting scripts run through Nx (pnpm nx run-many -t build, pnpm nx run ghost:build:tsc), and e2e/package.json adds Playwright-flavored end-to-end infrastructure scripts (infra:up/infra:down, build:docker producing a ghost-e2e image) plus Stripe environment provisioning (stripe:provision, stripe:fixtures) for testing paid memberships.

Self-hosting notes

Ghost supports self-hosting directly: the README documents the ghost-cli tool (npm install ghost-cli -g) for both ghost install local (quick local setup) and a full ghost install with automatic Let's Encrypt SSL for production servers, as an alternative to the managed Ghost(Pro) hosting option the project also sells. For contributors working in the monorepo itself, docker/ghost-dev/Dockerfile provides a source-mounted dev container, and compose-profile scripts (dev:sqlite, dev:mailgun, dev:fake-mailgun, dev:analytics, dev:storage, dev:stripe) let a developer opt individual optional services — SQLite, real or fake Mailgun, analytics, external storage, Stripe — in or out of the local stack independently.

What to copy (and what not to)

Splitting every embeddable public-facing widget (portal, comments-ui, sodo-search, signup-form, announcement-bar, admin-toolbar) into its own small Vite-built UMD package sharing one build config (configs/vite-public-app) is a strong pattern for any product that embeds scripts into third-party pages: sites only download what they actually use, and each widget can ship independently without redeploying the whole admin bundle. Running two full admin implementations (Ember and React) side by side in the same monorepo during a migration is a heavier commitment worth thinking carefully about before copying — it keeps the product shippable throughout a multi-year rewrite, but means maintaining build tooling, dependencies and test suites for both stacks simultaneously until the migration finishes.

Sources & repo audit

Audited
Sep 17, 2026
Commit
6f86cb6
License
MIT

Independent analysis of repository at github.com/TryGhost/Ghost. Spotted an inaccuracy? Use the claim form to request a correction.

Show this stack on your README or website
Tech stack of Ghost
[![Tech stack of Ghost on STACK IT FAST](https://stackitfast.com/badge/ghost.svg)](https://stackitfast.com/project/ghost)

Paste it into your README. It shows your current stack and links to this page.

Use this stack

Scaffold it with your agent

Paste this prompt into Claude Code, Cursor, Windsurf or AGY to start a project with Ghost's architecture.

  1. 1Copy the promptThe full markdown spec, with every layer and decision.
  2. 2Open your AI toolClaude Code, Cursor, Windsurf or Copilot, in a new repo.
  3. 3Paste and scaffoldUse it as the first instruction; review before you ship.
use-this-stack.md · 62 lines · 8.6 KB
# MISSION: Scaffold "Ghost" Production Architecture
You are an expert Senior Staff Software Architect and Full-Stack Engineer. Your mission is to scaffold and implement a production-grade, highly reliable, and modular codebase following the proven architecture of **Ghost**.
---
## 1. PROJECT SPECIFICATIONS & BENCHMARK
- **Reference Architecture**: Ghost
- **What It Does**: Ghost is an open-source, self-hostable Node.js publishing platform for blogs, newsletters and paid memberships, built as a pnpm/Nx monorepo containing a core server, a React admin app migrating off Ember, and a set of independently built embeddable widgets.
- **Domain & Category**: Content Platform
- **Production Scale**: 20+ people
- **Development Mode**: CLASSIC
- **Architectural Rationale**: Ghost runs its core server, a React admin app that is replacing a legacy Ember admin in place, and every public-facing widget as separately Vite-built packages inside one pnpm/Nx monorepo, so the publishing surface, the editor and each embeddable script can evolve and ship on their own schedule.
- **Live Website Reference**: https://ghost.org
- **Source Repository**: https://github.com/TryGhost/Ghost
---
## 2. PRODUCTION TECH STACK
- **Full Stack Array**: TypeScript, JavaScript, Node.js, React, Ember, Handlebars, Vite, pnpm, Nx, SQLite, Tinybird, ClickHouse, Docker
- **Primary Language(s)**: TypeScript, JavaScript
- **License of the reference repo**: MIT
- **Frontend**: React — New admin dashboard (apps/admin) built with Vite, TanStack Query and Radix UI primitives, replacing the Ember admin in place.; Ember — Legacy ghost-admin client (apps/ember-admin) still ships from the monorepo alongside the newer React admin during the migration.; Vite — Bundler for the new admin app and every embeddable widget (portal, comments-ui, sodo-search, signup-form, announcement-bar, activitypub).; Preact — Powers admin-toolbar, the lightweight staff toolbar injected into published site pages.; Handlebars — Templating language for server-rendered themes; the default Casper theme ships under ghost/core content/themes/casper.
- **Backend & APIs**: Node.js — ghost/core is the Node.js application server; the dev Docker image pins Node 22.; Ghost API framework — Internal @tryghost/api-framework and @tryghost/admin-api-schema packages provide shared REST request handling and JSON-schema validation for ghost/core.
- **Data & persistence**: SQLite — Selectable local/dev persistence backend (better-sqlite3) via a dedicated dev:sqlite compose profile.; ClickHouse — Storage engine underneath the local Tinybird analytics stack used to develop and test analytics events.; Tinybird — Real-time analytics platform for site/traffic analytics; e2e tests sync fixture state against a local Tinybird instance.
- **Infrastructure & deploy**: Docker — Dev container (docker/ghost-dev), a fake-mailgun stub for local email testing, and the flattened Tinybird/ClickHouse local image.; pnpm — Monorepo package manager with a strict dependency catalog (catalogMode: strict) shared across every workspace.
- **Tooling, testing & ops**: Nx — Task orchestrator running cached build/lint/test targets across every ghost/*, apps/*, packages/* and koenig/* workspace.; Vitest — Primary test runner for ghost/core and most apps/packages (test:watch, test:unit scripts).
---
## 3. KEY ARCHITECTURAL DECISIONS (audited from https://github.com/TryGhost/Ghost @ 6f86cb6)
1. **React admin is replacing Ember admin in place, not on a separate branch**: apps/admin (name @tryghost/admin, Vite + React + TanStack Query + Radix UI) and apps/ember-admin (name ghost-admin, Ember.js, version 6.64.1-rc.0) both live in the same monorepo and ship together, so the admin UI migrates screen by screen instead of as a big-bang rewrite.
2. **Every embeddable public widget is its own Vite-built UMD package**: portal, comments-ui, sodo-search, signup-form, announcement-bar, admin-toolbar and activitypub each declare a umd/ output and a shared build config from configs/vite-public-app/package.json, so a published Ghost site loads only the small standalone scripts it actually uses instead of one monolithic admin bundle.
3. **Site analytics run on Tinybird/ClickHouse, not an in-house pipeline**: docker/tinybird-local-slim/Dockerfile flattens a real Tinybird-local image (CLICKHOUSE_VERSION env var, MinIO-backed storage) for local and CI use, docker/tb-cli/Dockerfile installs the Tinybird CLI itself, and e2e/package.json exposes a tinybird:sync script to keep local Tinybird state aligned with test fixtures.
4. **pnpm plus Nx with a locked dependency catalog governs the whole monorepo**: pnpm-workspace.yaml sets catalogMode: strict, blockExoticSubdeps: true and a minimumReleaseAge of 3 days for new dependency versions, while nx.json defines cached build/lint/test targets that run across every workspace listed in pnpm-workspace.yaml (ghost/*, apps/*, packages/**, koenig/*, configs/*).
5. **ActivityPub support ships as its own app, not bolted onto ghost/core**: apps/activitypub is a separate @tryghost/activitypub package with its own Vite build and an ./api export consumed by apps/admin, keeping Fediverse protocol code out of the main ghost/core server package.
6. **The editor is a Lexical-based rewrite, kept compatible with the older Mobiledoc format**: apps/admin depends on @tryghost/koenig-lexical for its rich-text editor, while koenig/kg-default-cards still ships "Mobiledoc card definitions for Ghost's editor" (version 10.3.6) so posts authored under the previous Mobiledoc-based editor keep rendering.
7. **Local dev containerizes optional external dependencies, including email**: docker/fake-mailgun/Dockerfile provides a stand-in for Mailgun so pnpm dev:fake-mailgun works without real credentials, and root package.json defines separate compose-profile scripts (dev:sqlite, dev:mailgun, dev:analytics, dev:storage, dev:stripe) that opt individual optional services in or out of the dev stack.
---
## 4. NON-NEGOTIABLE ARCHITECTURAL GUARDRAILS
1. **Monorepo & Modular Separation**:
   - Structure as a Turborepo monorepo with strict package boundaries:
     - `apps/web`: Application UI, routing, layouts, and server endpoints.
     - `packages/ui`: Shared design tokens, CSS variables, and Radix UI primitive components.
     - `packages/db`: Database schemas, client singleton, declarative migrations, and seed scripts.
     - `packages/config`: Shared TypeScript, ESLint, and build configurations.
2. **Strict Type Safety & Zero `any` Policy**:
   - Enable `strict: true`, `noImplicitAny: true`, and `strictNullChecks: true`.
   - Validate ALL external inputs, API request bodies, and query parameters with **Zod** schemas before execution.
3. **Frontend & Rendering Guidelines**:
   - Utilize Vite 6 with TanStack Router for fully type-safe routing. Manage server state and caching via TanStack Query v5 with optimistic UI updates.
4. **Design System & Aesthetics**:
   - Keep every color, radius, shadow and font in a single token file (CSS variables) and consume tokens everywhere; never hardcode hex values in components.
   - Prefer crisp 1px borders and one subtle shadow scale over blurry default shadows. Pair one sans-serif for body/headings with one monospace for tags, badges, metrics, and code.
5. **Data Layer & Reliability**:
   - Write declarative schema definitions with foreign keys, composite indexes on queried filters, and automated timestamp triggers.
   - Use connection pooling and prepared statements for serverless database execution.
---
## 5. STEP-BY-STEP SCAFFOLDING ROADMAP
- **Phase 1: Workspace & Root Config**: Initialize package manager, monorepo configuration (`turbo.json`, `tsconfig.base.json`, `package.json`).
- **Phase 2: Database Schema & Client**: Set up the data layer (SQLite, ClickHouse, Tinybird): client, connection pool, models, and migration scripts.
- **Phase 3: Design Tokens & UI Primitives**: Build accessible `Button`, `Input`, `Card`, `Badge`, and layout wrappers inside `packages/ui`.
- **Phase 4: Core Application Routes & Handlers**: Implement primary authentication, user session handling, and application routes.
- **Phase 5: Quality Assurance & Build Verification**: Run `tsc --noEmit`, ESLint, Prettier, and smoke test suites to ensure zero compilation or runtime errors.
---
## 6. EXECUTION INSTRUCTIONS
1. Review all specifications, architectural guardrails, and stack choices above.
2. Present the full monorepo directory tree structure.
3. Systematically generate the complete, production-ready codebase according to the 5-phase roadmap above — starting with the root workspace setup, followed by the database schema, UI design system package, and full-stack application routes until the repository is fully scaffolded and ready to run.
Scaffolded something with this prompt?
Would you pick this stack for a content platform project?

Frequently asked about Ghost

What is Ghost built with?

Ghost is a Node.js and TypeScript monorepo managed with pnpm and Nx. The core server lives in ghost/core, the admin UI is migrating from Ember (apps/ember-admin) to React (apps/admin, Vite + TanStack Query + Radix UI), and public widgets like portal and comments-ui are separate Vite-built packages.

Does Ghost use React or Ember for its admin interface?

Both, during an in-progress migration. apps/ember-admin is the original Ember.js admin client (still versioned and shipped), while apps/admin is a newer React application built with Vite, TanStack Query and Radix UI that is replacing it screen by screen.

Does Ghost need a separate database?

The monorepo includes a SQLite dev profile (better-sqlite3, via a dev:sqlite compose file) for local development. Analytics data is handled separately through a Tinybird/ClickHouse pipeline rather than the primary content database.

Can I self-host Ghost?

Yes. The README documents both a CLI-based local/production install (ghost install, ghost install local) and Docker-based development via docker/ghost-dev/Dockerfile, alongside the managed Ghost(Pro) hosting option.

How does Ghost handle site analytics?

Through Tinybird, a real-time analytics platform built on ClickHouse. docker/tinybird-local-slim/Dockerfile packages a local Tinybird/ClickHouse image for development and CI, and e2e/package.json includes a tinybird:sync script to keep local analytics state in sync with test fixtures.

Is Ghost a monorepo?

Yes. pnpm-workspace.yaml defines workspaces under ghost/*, apps/*, packages/**, koenig/* and configs/*, and Nx (nx.json) orchestrates cached build, lint and test targets across all of them.

One email a month: new deep dives and stack trends

New source-audited architectures, head-to-head comparisons and the monthly stack report. No spam, unsubscribe anytime.

use-this-stack.md