I am trying to run ActivityPub myself next to my ghost instance.
It works kinda half way.
One thing I changed the volume to UPLOAD_LOCATION=./activitypub_data and I can not see any data in there.
The error also says something about that keys could not be found…
Here is the log output when I try to follow my mastodon account from the ghost afmin site.
activitypub-1 | 18:01:53.862 INF activitypub: 'POST' 'www.ajfriesen.com' 'https://www.ajfriesen.com/.ghost/activitypub/v1/actions/follow/@ajfriesen@techhub.social' '832f557c-5ebe-450a-a6b9-9d926e1e5752'
ghost-1 | [2025-09-18 18:01:53] INFO "GET /ghost/api/admin/identities/" 200 118ms
activitypub-1 | 18:01:53.864 INF activitypub: KnexKvStore: Get key cachedJwks,www.ajfriesen.com
activitypub-1 | 18:01:54.092 INF activitypub: Looking up actor locally ('https://www.ajfriesen.com/.ghost/activitypub/users/index')
activitypub-1 | 18:01:54.092 INF activitypub: KnexKvStore: Get key https://www.ajfriesen.com/.ghost/activitypub/users/index
activitypub-1 | 18:01:54.101 INF activitypub: Looking up actor locally ('https://techhub.social/users/ajfriesen')
activitypub-1 | 18:01:54.101 INF activitypub: KnexKvStore: Get key https://techhub.social/users/ajfriesen
activitypub-1 | 18:01:54.149 INF activitypub: KnexKvStore: Set key https://www.ajfriesen.com/.ghost/activitypub/follow/7814fd51-bb56-43a2-9d9d-5ee836b563e0
activitypub-1 | 18:01:54.179 WRN fedify·federation·outbox: No supported key found to create a proof for the activity 'https://www.ajfriesen.com/.ghost/activitypub/follow/7814fd51-bb56-43a2-9d9d-5ee836b563e0'. The activity will be sent without a proof. In order to create a proof, at least one Ed25519 key must be provided.
activitypub-1 | 18:01:54.181 INF activitypub: KnexKvStore: Get key _fedify,httpMessageSignaturesSpec,https://techhub.social
activitypub-1 | 18:01:54.760 INF activitypub: 'GET' 'www.ajfriesen.com' 'https://www.ajfriesen.com/.ghost/activitypub/users/index' 'b86d1e5a-4699-4c30-b272-c2cb48b48d2a'
activitypub-1 | 18:01:54.835 WRN fedify·federation·actor: You configured inbox listeners, but the actor does not have a endpoints.sharedInbox property. Set the property with Context.getInboxUri().
activitypub-1 | 18:01:54.835 WRN fedify·federation·actor: You configured a key pairs dispatcher, but the actor does not have an assertionMethod property. Set the property with Context.getActorKeyPairs(identifier).
activitypub-1 | 18:01:54.842 INF activitypub: 'GET' 'www.ajfriesen.com' 'https://www.ajfriesen.com/.ghost/activitypub/users/index' 'b86d1e5a-4699-4c30-b272-c2cb48b48d2a' 200 82ms
activitypub-1 | 18:01:55.204 INF activitypub: 'GET' 'www.ajfriesen.com' 'https://www.ajfriesen.com/.ghost/activitypub/users/index' 'df9f98dd-bd94-40e6-ae9c-454fff1256d0'
activitypub-1 | 18:01:55.261 WRN fedify·federation·actor: You configured inbox listeners, but the actor does not have a endpoints.sharedInbox property. Set the property with Context.getInboxUri().
activitypub-1 | 18:01:55.261 WRN fedify·federation·actor: You configured a key pairs dispatcher, but the actor does not have an assertionMethod property. Set the property with Context.getActorKeyPairs(identifier).
activitypub-1 | 18:01:55.266 INF activitypub: 'GET' 'www.ajfriesen.com' 'https://www.ajfriesen.com/.ghost/activitypub/users/index' 'df9f98dd-bd94-40e6-ae9c-454fff1256d0' 200 62ms
activitypub-1 | 18:01:55.669 INF activitypub: 'GET' 'www.ajfriesen.com' 'https://www.ajfriesen.com/.well-known/webfinger?resource=acct:index@www.ajfriesen.com' '4ab013d2-a754-4d42-a938-2d4ca6526acc'
activitypub-1 | 18:01:55.688 INF activitypub: 'GET' 'www.ajfriesen.com' 'https://www.ajfriesen.com/.well-known/webfinger?resource=acct:index@www.ajfriesen.com' '4ab013d2-a754-4d42-a938-2d4ca6526acc' 200 19ms
ghost-1 | [2025-09-18 18:01:56] INFO "GET /.well-known/webfinger?resource=acct:index@ajfriesen.com" 301 27ms
ghost-1 | [2025-09-18 18:01:56] INFO "GET /.well-known/webfinger/?resource=acct:index@ajfriesen.com" 404 24ms
ghost-1 | [2025-09-18 18:01:56] INFO "GET /.well-known/host-meta" 301 1ms
ghost-1 | [2025-09-18 18:01:57] INFO "GET /.well-known/host-meta/" 404 44ms
activitypub-1 | 18:01:57.619 ERR fedify·federation·outbox: Failed to send activity 'https://www.ajfriesen.com/.ghost/activitypub/follow/7814fd51-bb56-43a2-9d9d-5ee836b563e0' to 'https://techhub.social/users/ajfriesen/inbox' (401 'Unauthorized'):
activitypub-1 | '{"error":"Error parsing signature parameters"}'
activitypub-1 | 18:01:57.626 ERR activitypub: Error: Failed to send activity https://www.ajfriesen.com/.ghost/activitypub/follow/7814fd51-bb56-43a2-9d9d-5ee836b563e0 to https://techhub.social/users/ajfriesen/inbox (401 Unauthorized):
activitypub-1 | {"error":"Error parsing signature parameters"}
activitypub-1 | at sendActivityInternal (file:///opt/activitypub/node_modules/@fedify/fedify/dist/middleware-BPdRnvp4.js:1745:9)
activitypub-1 | at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
activitypub-1 | at async file:///opt/activitypub/node_modules/@fedify/fedify/dist/middleware-BPdRnvp4.js:1677:4
activitypub-1 | at async Promise.all (index 0)
activitypub-1 | at async FederationImpl.sendActivity (file:///opt/activitypub/node_modules/@fedify/fedify/dist/middleware-BPdRnvp4.js:2313:4)
activitypub-1 | at async RequestContextImpl.sendActivityInternal (file:///opt/activitypub/node_modules/@fedify/fedify/dist/middleware-BPdRnvp4.js:3073:4)
activitypub-1 | at async file:///opt/activitypub/node_modules/@fedify/fedify/dist/middleware-BPdRnvp4.js:2992:5
activitypub-1 | at async FollowController.handleFollow (file:///opt/activitypub/dist/app.js:5625:5)
activitypub-1 | at async dispatch (file:///opt/activitypub/node_modules/hono/dist/compose.js:22:17)
activitypub-1 | at async dispatch (file:///opt/activitypub/node_modules/hono/dist/compose.js:22:17)
activitypub-1 | 18:01:57.630 INF activitypub: 'POST' 'www.ajfriesen.com' 'https://www.ajfriesen.com/.ghost/activitypub/v1/actions/follow/@ajfriesen@techhub.social' '832f557c-5ebe-450a-a6b9-9d926e1e5752' 500 3767ms
Here is my compose:
services:
ghost:
image: ghost:${GHOST_VERSION:-6-alpine}
restart: always
env_file:
- .env
volumes:
- ./data:/var/lib/ghost/content
# links:
# - db
networks:
- proxy-network
environment:
NODE_ENV: production
url: https://${DOMAIN:?DOMAIN environment variable is required}
admin__url: ${ADMIN_DOMAIN:+https://${ADMIN_DOMAIN}}
database__client: mysql
database__connection__host: blog-db
database__connection__user: ${DATABASE_USER:-ghost}
database__connection__password: ${DATABASE_PASSWORD:?DATABASE_PASSWORD environment variable is required}
database__connection__database: ghost
tinybird__tracker__endpoint: https://${DOMAIN:?DOMAIN environment variable is required}/.ghost/analytics/api/v1/page_hit
tinybird__adminToken: ${TINYBIRD_ADMIN_TOKEN:-}
tinybird__workspaceId: ${TINYBIRD_WORKSPACE_ID:-}
tinybird__tracker__datasource: analytics_events
tinybird__stats__endpoint: ${TINYBIRD_API_URL:-https://api.tinybird.co}
depends_on:
db:
condition: service_healthy
tinybird-sync:
condition: service_completed_successfully
required: false
tinybird-deploy:
condition: service_completed_successfully
required: false
activitypub:
condition: service_started
required: false
labels:
- "traefik.enable=true"
- "traefik.http.routers.blog.rule=Host(`www.ajfriesen.com`) || Host(`ajfriesen.com`)"
- "traefik.http.routers.blog.entrypoints=websecure"
- "traefik.http.routers.blog.tls.certresolver=dnsresolver"
- "traefik.http.routers.blog.tls=true"
- "traefik.http.routers.blog.service=blog"
- "traefik.http.services.blog.loadbalancer.server.port=2368"
- "traefik.http.routers.blog-ap-main.priority=10"
# Redirection from ajfriesen.com to www.ajfriesen.com
- "traefik.http.routers.redirect.rule=Host(`ajfriesen.com`)"
- "traefik.http.middlewares.redirect-to-www.redirectregex.regex=^https://ajfriesen.com/(.*)"
- "traefik.http.middlewares.redirect-to-www.redirectregex.replacement=https://www.ajfriesen.com/$${1}"
- "traefik.http.middlewares.redirect-to-www.redirectregex.permanent=true"
- "traefik.http.routers.redirect.middlewares=redirect-to-www"
db:
container_name: blog-db
image: mysql:8.0
restart: always
command: --default-authentication-plugin=mysql_native_password
environment:
MYSQL_ROOT_PASSWORD: ${DATABASE_ROOT_PASSWORD:?DATABASE_ROOT_PASSWORD environment variable is required}
MYSQL_USER: ${DATABASE_USER:-ghost}
MYSQL_PASSWORD: ${DATABASE_PASSWORD:?DATABASE_PASSWORD environment variable is required}
MYSQL_DATABASE: ghost
MYSQL_MULTIPLE_DATABASES: activitypub
volumes:
- ./database:/var/lib/mysql
- ./mysql-init:/docker-entrypoint-initdb.d
healthcheck:
# test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
test: mysqladmin ping -p$$MYSQL_ROOT_PASSWORD -h blog-db
# test: mysqladmin ping -h localhost
interval: 1s
start_period: 30s
start_interval: 10s
retries: 120
networks:
- proxy-network
activitypub:
image: ghcr.io/tryghost/activitypub:1.1.0@sha256:39c212fe23603b182d68e67d555c6b9b04b1e57459dfc0bef26d6e4980eb04d1
restart: always
# expose:
# - "8080"
volumes:
- ${UPLOAD_LOCATION:-./data/ghost}:/opt/activitypub/content
environment:
NODE_ENV: production
ACTIVITYPUB_COLLECTION_PAGE_SIZE: 20
MYSQL_HOST: blog-db
MYSQL_USER: ${DATABASE_USER:-ghost}
MYSQL_PASSWORD: ${DATABASE_PASSWORD:?DATABASE_PASSWORD environment variable is required}
MYSQL_DATABASE: activitypub
ALLOW_PRIVATE_ADDRESS: true
USE_MQ: false
LOCAL_STORAGE_PATH: /opt/activitypub/content/images/activitypub
LOCAL_STORAGE_HOSTING_URL: https://${DOMAIN}/content/images/activitypub
depends_on:
db:
condition: service_healthy
activitypub-migrate:
condition: service_completed_successfully
profiles: [activitypub]
networks:
- proxy-network
labels:
- "traefik.enable=true"
- "traefik.http.services.blog-activitypub.loadbalancer.passhostheader=true"
- "traefik.http.services.blog-activitypub.loadbalancer.server.port=8080"
- "traefik.http.routers.blog-activitypub.entrypoints=websecure"
- "traefik.http.routers.blog-activitypub.service=blog-activitypub"
- "traefik.http.routers.blog-activitypub.tls=true"
- "traefik.http.routers.blog-activitypub.tls.certresolver=dnsresolver"
- "traefik.http.routers.blog-activitypub.rule=Host(`www.ajfriesen.com`) && (PathPrefix(`/.ghost/activitypub/`) || Path(`/.well-known/webfinger`) || Path(`/.well-known/nodeinfo`))"
activitypub-migrate:
image: ghcr.io/tryghost/activitypub-migrations:1.1.0@sha256:b3ab20f55d66eb79090130ff91b57fe93f8a4254b446c2c7fa4507535f503662
environment:
MYSQL_DB: mysql://${DATABASE_USER:-ghost}:${DATABASE_PASSWORD:?DATABASE_PASSWORD environment variable is required}@tcp(blog-db:3306)/activitypub
networks:
- proxy-network
depends_on:
db:
condition: service_healthy
profiles: [activitypub]
restart: no
networks:
proxy-network:
external: true
and part of my .env:
UPLOAD_LOCATION=./activitypub_data
ACTIVITYPUB_TARGET=activitypub:8080
DOMAIN=www.ajfriesen.com
COMPOSE_PROFILES=activitypub
Does anybody have an idea where I can check further to get this running?
Is this an ActivityPub problem or a routing problem, because I use Traefik?