diff --git a/compose.dokploy.yml b/compose.dokploy.yml new file mode 100644 index 0000000..9fc891d --- /dev/null +++ b/compose.dokploy.yml @@ -0,0 +1,248 @@ +--- +# 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} + 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:?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 + 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: ${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 + 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(${MYSQL_HOST:-db}:3306)/activitypub + networks: + - dokploy-network + depends_on: + db: + condition: service_healthy + required: false + profiles: [activitypub] + restart: no + +volumes: + tinybird_files: + tinybird_home: + traffic_analytics_data: + +networks: + dokploy-network: + external: true