--- # yaml-language-server: $schema=https://raw.githubusercontent.com/compose-spec/compose-spec/main/schema/compose-spec.json # Docker Compose file for Dokploy deployments using Traefik # This file removes Caddy and uses Traefik labels for reverse proxy configuration services: ghost: # Do not alter this without updating the Tinybird Sync container as well image: ghost:${GHOST_VERSION:-6-alpine} restart: always # Optional: Import config from .env file if it exists (for local development or migration) env_file: - path: .env required: false 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: ${DATABASE_HOST:-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} mail__transport: ${MAIL_TRANSPORT} mail__options__service: ${MAIL_SERVICE} mail__options__host: ${MAIL_HOST} mail__options__port: ${MAIL_PORT} mail__options__secure: ${MAIL_SECURE} mail__options__auth__user: ${MAIL_USER} mail__options__auth__pass: ${MAIL_PASSWORD} mail__from: ${MAIL_FROM} ACTIVITYPUB_TARGET: ${ACTIVITYPUB_TARGET} COMPOSE_PROFILES: ${COMPOSE_PROFILES} volumes: - ${UPLOAD_LOCATION:-./data/ghost}:/var/lib/ghost/content depends_on: db: condition: service_healthy required: false tinybird-sync: condition: service_completed_successfully required: false tinybird-deploy: condition: service_completed_successfully required: false activitypub: condition: service_started required: false networks: - dokploy-network labels: - "traefik.enable=true" - "traefik.http.routers.ghost.rule=Host(`${DOMAIN}`)" - "traefik.http.routers.ghost.entrypoints=websecure" - "traefik.http.routers.ghost.tls.certresolver=letsencrypt" - "traefik.http.services.ghost.loadbalancer.server.port=2368" # Security Headers - "traefik.http.middlewares.security-headers.headers.customresponseheaders.Strict-Transport-Security=max-age=31536000" - "traefik.http.middlewares.security-headers.headers.customresponseheaders.X-XSS-Protection=1; mode=block" - "traefik.http.middlewares.security-headers.headers.customresponseheaders.X-Content-Type-Options=nosniff" - "traefik.http.middlewares.security-headers.headers.customresponseheaders.Referrer-Policy=strict-origin-when-cross-origin" # Compression - "traefik.http.middlewares.gzip.compress=true" # Apply middlewares - "traefik.http.routers.ghost.middlewares=security-headers,gzip" db: image: mysql:8.0.44@sha256:f37951fc3753a6a22d6c7bf6978c5e5fefcf6f31814d98c582524f98eae52b21 restart: always expose: - "3306" environment: MYSQL_ROOT_PASSWORD: ${DATABASE_ROOT_PASSWORD} MYSQL_USER: ${DATABASE_USER:-ghost} MYSQL_PASSWORD: ${DATABASE_PASSWORD} 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 profiles: [mysql] networks: - dokploy-network traffic-analytics: image: ghost/traffic-analytics:1.0.20@sha256:a72573d89457e778b00e9061422516d2d266d79a72a0fc02005ba6466e391859 restart: always expose: - "3000" volumes: - traffic_analytics_data:/data environment: NODE_ENV: production PROXY_TARGET: ${TINYBIRD_API_URL:-https://api.tinybird.co}/v0/events SALT_STORE_TYPE: ${SALT_STORE_TYPE:-file} SALT_STORE_FILE_PATH: /data/salts.json TINYBIRD_TRACKER_TOKEN: ${TINYBIRD_TRACKER_TOKEN:-} LOG_LEVEL: debug profiles: [analytics] networks: - dokploy-network labels: - "traefik.enable=true" # Analytics endpoint - route /.ghost/analytics/* to traffic-analytics service - "traefik.http.routers.traffic-analytics.rule=Host(`${DOMAIN}`) && PathPrefix(`/.ghost/analytics`)" - "traefik.http.routers.traffic-analytics.entrypoints=websecure" - "traefik.http.routers.traffic-analytics.tls.certresolver=letsencrypt" - "traefik.http.routers.traffic-analytics.priority=100" - "traefik.http.services.traffic-analytics.loadbalancer.server.port=3000" # Strip the /.ghost/analytics prefix before forwarding - "traefik.http.middlewares.strip-analytics.stripprefix.prefixes=/.ghost/analytics" - "traefik.http.routers.traffic-analytics.middlewares=strip-analytics,security-headers" activitypub: image: ghcr.io/tryghost/activitypub:1.1.0@sha256:39c212fe23603b182d68e67d555c6b9b04b1e57459dfc0bef26d6e4980eb04d1 restart: always expose: - "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 MYSQL_HOST: ${DATABASE_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 required: false activitypub-migrate: condition: service_completed_successfully profiles: [activitypub] networks: - dokploy-network labels: - "traefik.enable=true" # ActivityPub endpoints - "traefik.http.routers.activitypub.rule=Host(`${DOMAIN}`) && PathPrefix(`/.ghost/activitypub`)" - "traefik.http.routers.activitypub.entrypoints=websecure" - "traefik.http.routers.activitypub.tls.certresolver=letsencrypt" - "traefik.http.routers.activitypub.priority=100" # WebFinger endpoint - "traefik.http.routers.webfinger.rule=Host(`${DOMAIN}`) && Path(`/.well-known/webfinger`)" - "traefik.http.routers.webfinger.entrypoints=websecure" - "traefik.http.routers.webfinger.tls.certresolver=letsencrypt" - "traefik.http.routers.webfinger.priority=100" # NodeInfo endpoint - "traefik.http.routers.nodeinfo.rule=Host(`${DOMAIN}`) && Path(`/.well-known/nodeinfo`)" - "traefik.http.routers.nodeinfo.entrypoints=websecure" - "traefik.http.routers.nodeinfo.tls.certresolver=letsencrypt" - "traefik.http.routers.nodeinfo.priority=100" - "traefik.http.services.activitypub.loadbalancer.server.port=8080" - "traefik.http.routers.activitypub.middlewares=security-headers" - "traefik.http.routers.webfinger.middlewares=security-headers" - "traefik.http.routers.nodeinfo.middlewares=security-headers" # Supporting Services tinybird-login: build: context: ./tinybird dockerfile: Dockerfile working_dir: /home/tinybird command: /usr/local/bin/tinybird-login volumes: - tinybird_home:/home/tinybird - tinybird_files:/data/tinybird profiles: [analytics] networks: - dokploy-network tty: false restart: no tinybird-sync: # Do not alter this without updating the Ghost container as well image: ghost:${GHOST_VERSION:-6-alpine} command: > sh -c " if [ -d /var/lib/ghost/current/core/server/data/tinybird ]; then rm -rf /data/tinybird/*; cp -rf /var/lib/ghost/current/core/server/data/tinybird/* /data/tinybird/; echo 'Tinybird files synced into shared volume.'; else echo 'Tinybird source directory not found.'; fi " volumes: - tinybird_files:/data/tinybird depends_on: tinybird-login: condition: service_completed_successfully networks: - dokploy-network profiles: [analytics] restart: no tinybird-deploy: build: context: ./tinybird dockerfile: Dockerfile working_dir: /data/tinybird command: > sh -c " tb-wrapper --cloud deploy " volumes: - tinybird_home:/home/tinybird - tinybird_files:/data/tinybird depends_on: tinybird-sync: condition: service_completed_successfully profiles: [analytics] networks: - dokploy-network tty: true 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(${DATABASE_HOST:-db}:3306)/activitypub networks: - dokploy-network depends_on: db: condition: service_healthy required: false profiles: [activitypub] restart: no volumes: ghost: tinybird_files: tinybird_home: traffic_analytics_data: networks: dokploy-network: external: true