Β
ΒA multi-tenant ActivityPub server for Ghost, built with Fedify. This service makes it possible for independent websites to publish their content directly to the Fediverse, enabling networked publishing to the open social web.
All requests to /.ghost/activitypub/*, /.well-known/webfinger and /.well-known/nodeinfo are proxied to this ActivityPub service using nginx. All other requests are forwarded to Ghost. This setup has only been tested on macOS using Docker for Mac and OrbStack.
- Install Ghost using
Install from sourceinstructions - Expose your local port 80 (nginx) with Tailscale (or ngrok)
- Use
tailscale funnel 80orngrok http 80to expose your local port 80 (not 2368!), on which nginx will be running
- Use
- Configure Ghost
- In the Ghost monorepo, create a
config.local.jsonfile underghost/core/config.local.json, with the following configuration:
{ "url": <Tailscale or ngrok URL from step 2> } - In the Ghost monorepo, create a
- Start the ActivityPub Service
- Run
pnpm dev && pnpm logsin the root directory of this project
- Run
- Start Ghost
- Run
yarn devin the Ghost monorepo - If you were already running Ghost locally, make sure to restart it!
- Run
For AI assistants: See AGENTS.md for comprehensive guidance with code examples. For developers: See Architecture Decision Records in
/adrfor detailed rationale.
This service follows Domain-Driven Design with specific patterns:
- Immutable Entities with Events (ADR-0003) - Entities return new instances with domain events
- Result Type Pattern (ADR-0004) - Use Result<T, E> for explicit error handling
- Error Objects in Results (ADR-0005) - Enhanced Result types with contextual error objects
- Class-Based Architecture (ADR-0006) - All components use classes with dependency injection
- Repository Pattern (ADR-0007) - Services orchestrate logic, repositories handle data access
- View Pattern for Reads (ADR-0008) - Optimized read queries separate from write path
- Hash-Based Lookups (ADR-0009)
β οΈ - ActivityPub IDs use SHA256 hashes - Decorator Routing (ADR-0010) - Routes defined via decorators
src/
βββ account/ # Immutable entities
βββ post/ # Being migrated to immutable
βββ activity-handlers/ # Class-based handlers
βββ http/api/ # REST controllers
βββ core/ # Shared utilities
βββ dispatchers.ts # Legacy - don't add here
1. Database lookups MUST use SHA256 hashes (ADR-0009)
- Never use
where('ap_id', apId)- it returns empty results silently! - Always use
whereRaw('ap_id_hash = UNHEX(SHA2(?, 256))', [apId]) - Applies to:
ap_id,domain(with LOWER),ap_inbox_url(with LOWER)
2. Result types require helper functions
- Use
isError(result),getValue(result),getError(result) - Never destructure directly like
[error, value]
3. Services must use repositories
- Views can query DB directly (read optimization)
- Services MUST go through repositories (write path)
4. Dependency injection names must match
- Parameter
accountServiceβ registered as'accountService' - Parameter
dbβ registered as'db'
5. Avoid these anti-patterns:
- Adding to
dispatchers.tsβ create new handler classes - Using
AccountTypeβ useAccountentity - Direct DB queries in services β use repositories
- String comparisons for AP IDs β use hash lookups
For complete code examples demonstrating correct patterns, see AGENTS.md.
We use Biome for code formatting and linting.
If you use VS Code, you can install the Biome extension to get inline feedback.
To enable auto-formatting on save, you'll need to set the default formatter to Biome and enable Format on Save in your VS Code settings.
Tests run within a Docker Compose stack. Common commands:
pnpm testβ run everything (types, unit, integration, and e2e); slowpnpm test:unitβ unit tests only; fastpnpm test:integrationβ integration tests onlypnpm test:cucumberβ end-to-end (Cucumber) testspnpm test:single 'path/to/test'β run a single unit or integration testpnpm test:typesβ type-check withtsc
See AGENTS.md for the full list and testing conventions.
Run pnpm migrate to apply pending up migrations against your dev db (this also happens automatically on pnpm dev). For the testing db, use docker compose run migrate-testing up.
To run other migrate commands, drop into a shell with docker compose exec -it migrate /bin/bash (or migrate-testing). The migrate binary is available there, along with a MYSQL_DB environment variable correctly formatted for the -database argument.
Β
Copyright (c) 2013-2026 Ghost Foundation - Released under the MIT license. Ghost and the Ghost Logo are trademarks of Ghost Foundation Ltd. Please see our trademark policy for info on acceptable usage.