Traffic Analytics Service - A web analytics proxy for Ghost that processes and enriches traffic data before forwarding it to Tinybird's analytics API.
The following sequence diagram shows a simplified overview of where the Analytics Service fits in to Ghost's traffic analytics features.
- A user requests a Ghost site's homepage (or any other page on the site's frontend)
- Ghost serves the page's HTML, plus a script called
ghost-stats.js - The
ghost-stats.jsscript executes and sends aPOSTrequest to the Analytics Service'sPOST /api/v1/page_hitendpoint - The Analytics Service receives the request and processes it. This includes parsing the user agent, generating a user signature, etc.
- The Analytics Service proxies the request to Tinybird
- Tinybird receives the request and stores it in its Clickhouse database
- The Analytics Service then proxies the response from Tinybird back to the user's browser.
sequenceDiagram
autonumber
participant User as User Browser
participant Ghost as Ghost Site
participant AS as Analytics Service
participant TB as Tinybird
User->>Ghost: GET /
activate Ghost
Ghost-->>User: HTML + ghost-stats.js
deactivate Ghost
Note over User: ghost-stats.js executes
User->>+AS: POST /api/v1/page_hit
AS->>AS: Process Request
AS->>+TB: POST /v0/events<br/>Enriched analytics data
TB-->>-AS: 202 Accepted
AS-->>-User: 202 Accepted
Note over User,TB: Analytics event successfully tracked
The "Process Request" and "forward to Tinybird" steps above happen in one of two ways, and this is how the service runs by default in development and production:
- Batch mode (default) — The ingest service validates the request, publishes the raw event to a Google Cloud Pub/Sub topic, and immediately returns
202. A separate worker process consumes from the subscription, enriches each event (user-agent parsing, referrer parsing, user signature), filters bot traffic, batches events, and forwards them to Tinybird's/v0/eventsendpoint. This decouples request handling from Tinybird ingestion. Started withyarn dev(alias foryarn dev:batch). - Proxy mode (synchronous) — With no Pub/Sub topic configured, the ingest service enriches the request inline and proxies it straight to Tinybird in the same request/response cycle. Started with
yarn dev:proxy.
Both modes run from the same image; the role is selected by the WORKER_MODE environment variable (worker vs. ingest) and the presence of PUBSUB_TOPIC_PAGE_HITS_RAW (batch vs. proxy). See docs/architecture.md for a diagram and full detail.
- User agent parsing for OS, browser, and device detection
- Referrer URL parsing and categorization
- Privacy-preserving user signatures with daily-rotating salts
Copy .env.example to .env and configure as needed. For local development with Ghost, see Develop locally with Ghost
Pre-requisites:
- A container runtime, such as Docker Desktop or Orbstack
- Docker Compose
git clonethis repo &cdinto it as usualyarn devto build & start all required development services. The Analytics Service will be reachable athttp://localhost:3000.
If you want to manually test the Analytics Service + Ghost together locally, there are just a few more steps to follow. You'll need this repo and TryGhost/Ghost cloned locally.
- In Ghost, add
ANALYTICS_PROXY_TARGET=traffic-analytics-analytics-service-1:3000to your.envfile. This tells Ghost'scaddyservice to route requests to/.ghost/analytics/**to this instance of the analytics service instead of the instance in Ghost's compose project. - In Ghost, run
docker compose --profile analytics up -d. This startstinybird-local, deploys the Tinybird schema, and stored required tokens in ashared-confignamed volume. - In Ghost, run
docker compose --profile split up. This runs Caddy, Ghost's backend and Ghost Admin. Ghost will be available athttp://localhost:2368 - In this repo, run
yarn dev:ghostinstead ofyarn dev. This runs the analytics service within Ghost's docker network, and mounts theshared-configvolume so it can access the tokens it needs to send events totinybird-local.
That's it! Now when you visit Ghost at http://localhost:2368, you should see the requests to /.ghost/analytics/api/v1/page_hit in the request logs in this repo, and the worker service will send the events to the tinybird-local service running in the Ghost project.
yarn test:types— run Typescript typechecks in Dockeryarn test:unit— run all unit tests in Dockeryarn test:integration— run all integration tests in Dockeryarn test— run typechecks, unit tests and integration tests in Dockeryarn test:e2e— run e2e tests (with wiremock) in Docker
yarn lintrun eslint in docker compose
This project supports running multiple worktrees simultaneously using Docker Compose. Each worktree can run its own isolated development environment with unique ports and container names.
- Create worktrees as usual with git worktree
- Configure each worktree with a unique
.envfile:
# main worktree (.env) - uses defaults
NODE_ENV=development
# work worktree (.env)
NODE_ENV=development
COMPOSE_PROJECT_NAME=traffic-analytics-work
ANALYTICS_PORT=3001
FIRESTORE_PORT=8081
# scratch worktree (.env)
NODE_ENV=development
COMPOSE_PROJECT_NAME=traffic-analytics-scratch
ANALYTICS_PORT=3002
FIRESTORE_PORT=8082Each worktree runs completely isolated:
- Unique ports: No conflicts between worktrees
- Isolated containers: Auto-generated names like
traffic-analytics-work-analytics-service-1 - Separate volumes: Each worktree has its own
node_modulesvolume - Independent projects: Services can run simultaneously
# Start development in any worktree
cd /path/to/worktree
docker compose up
# Each worktree accessible on its configured port
# main: http://localhost:3000
# work: http://localhost:3001
# scratch: http://localhost:3002- Create a branch and make your changes
- Open a PR against
main - Optionally test on staging by adding the
deploy-staginglabel to your PR- This deploys your branch to staging without merging
- The label is automatically removed after deployment
- Merge to main when ready
When a PR is merged to main, the following happens automatically:
- Version bump — The patch version is automatically incremented (e.g., 1.2.3 → 1.2.4)
- Tag creation — A git tag is created and pushed (e.g.,
v1.2.4) - Docker Hub — The image is published to Docker Hub
- Deploy to staging and production — Both environments are deployed in parallel
- Health checks — Automated health checks run against both environments
- Slack notification — The team is notified of the new release
You can manually trigger a deployment via the GitHub Actions UI by running the "Deploy" workflow with workflow_dispatch.
For the full pipeline (version bump, image build/push to GCP Artifact Registry, Docker Hub release, Cloud Run deploy, health checks, Slack notifications, and rollback), see docs/deployment.md.
- docs/architecture.md — run modes (batch vs. proxy), the Pub/Sub pipeline, salt-store adapters, OpenTelemetry, and the worker.
- docs/deployment.md — CI and deployment pipeline, staging/production,
deploy-staginglabel, and rollback.
Copyright (c) 2013-2026 Ghost Foundation - Released under the MIT license.