Finally I had installed Ghost 6 with Activitypub local server on Docker without using Caddy and/or Traeffik.
I can see the Network UI and configure my profile.
Everything seems to be fine but I see 404 errors on specific pages (/activitypub/v1/site /activitypub/v1/recommendations /activitypub/v1/topics).
curl -i “``https://mydomain.tld.ghost/activitypub/v1/site``” shows {“error”:“Forbidden”,“code”:“ROLE_MISSING”}
docker activitypub container shows ERR activitypub: User role ‘Anonymous’ is not allowed to access this resource
I wonder what could be wrong? That’s my files:
services:
ghost:
image: ghost:${GHOST_VERSION:-6-alpine}
restart: always
ports:
- "2368:2368"
env_file:
- .env
environment:
NODE_ENV: production
url: https://${DOMAIN:?DOMAIN environment variable is required}
admin__url: ${ADMIN_DOMAIN:+https://${ADMIN_DOMAIN}}
ap__enabled: "true"
ap__domain: "mydomain.tld"
ap__actor: "https://mydomain.tld/activitypub/actor"
ap__storage_adapter: "filesystem"
ap__storage_filesystem__path: "/var/lib/ghost/activitypub"
database__client: mysql
database__connection__host: db
database__connection__user: ${DATABASE_USER:-ghost}
database__connection__password: ${DATABASE_PASSWORD:?DATABASE_PASSWORD environment variable is required}
database__connection__database: ghost
volumes:
- ${UPLOAD_LOCATION:-./data/ghost}:/var/lib/ghost/content
- ap_data:/var/lib/ghost/activitypub
depends_on:
db:
condition: service_healthy
activitypub:
condition: service_started
required: false
networks:
- ghost_network
db:
image: mysql:8.0.42@sha256:4445b2668d41143cb50e471ee207f8822006249b6859b24f7e12479684def5d9
restart: always
expose:
- "3306"
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:
- ${MYSQL_DATA_LOCATION:-./data/mysql}:/var/lib/mysql
- ./mysql-init:/docker-entrypoint-initdb.d
healthcheck:
test: mysqladmin ping -p$$MYSQL_ROOT_PASSWORD -h 127.0.0.1
interval: 1s
start_period: 30s
start_interval: 10s
retries: 120
networks:
- ghost_network
activitypub:
image: ghcr.io/tryghost/activitypub:1.1.0@sha256:39c212fe23603b182d68e67d555c6b9b04b1e57459dfc0bef26d6e4980eb04d1
restart: always
ports:
- "8080:8080"
volumes:
- ${UPLOAD_LOCATION:-./data/ghost}:/opt/activitypub/content
environment:
# See https://github.com/TryGhost/ActivityPub/blob/main/docs/env-vars.md
NODE_ENV: production
ACTIVITYPUB_COLLECTION_PAGE_SIZE: 20
MYSQL_HOST: db
MYSQL_USER: ${DATABASE_USER:-ghost}
MYSQL_PASSWORD: ${DATABASE_PASSWORD:?DATABASE_PASSWORD environment variable is required}
MYSQL_DATABASE: activitypub
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:
- ghost_network
# 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(db:3306)/activitypub
# networks:
# - ghost_network
# depends_on:
# db:
# condition: service_healthy
# profiles: [activitypub]
# restart: no
volumes:
ap_data:
networks:
ghost_network:
server {
listen 443 ssl http2;
server_name mydomain.tld;
ssl_certificate /etc/letsencrypt/live/mydomain.tld/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mydomain.tld/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
client_max_body_size 300m;
location ^~ /.ghost/activitypub/ {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header Authorization $http_authorization;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Websocket support (often needed for real-time status)
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_no_cache 1;
proxy_cache_bypass 1;
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
location ~ ^/.well-known/(webfinger|nodeinfo) {
proxy_set_header Authorization $http_authorization;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $http_host;
# proxy_ssl_server_name on;
proxy_pass http://127.0.0.1:8080;
# add_header X-Content-Type-Options nosniff;
}
# Enable CORS for ActivityPub inbox and outbox if desired
location ~* ^/activitypub/(inbox|outbox|followers) {
add_header Access-Control-Allow-Origin *;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://127.0.0.1:8080;
}
location / {
proxy_pass http://127.0.0.1:2368;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_redirect off;
proxy_buffering off;
proxy_read_timeout 300s;
# WebSocket support for real-time admin (test ghost_backend)
location /ghost/socket {
proxy_pass http://127.0.0.1:2368;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
}
}
proxy_connect_timeout 90s;
proxy_send_timeout 90s;
proxy_read_timeout 90s;
add_header Access-Control-Allow-Origin mydomain.tld;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src * data: blob: 'unsafe-inline' 'unsafe-eval'";
add_header Content-Security-Policy "img-src * https: blob: data:";
#add_header Content-Security-Policy "default-src https: data:";
add_header Content-Security-Policy "object-src 'none'";
add_header Content-Security-Policy "script-src MYSCRIPTS 'unsafe-inline'";
add_header Content-Security-Policy "style-src 'unsafe-inline' https:";
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header X-Frame-Options "SAMEORIGIN";
proxy_hide_header X-Frame-Options;
}