Digital Sovereign Ghost
Why and How I Moved My Analytics Backend to a Local Deployment
Ghost is a wonderful blogging and publishing platform and running an own modern blog is more than just putting a Content Management System on a VPS or use a dedicated provider. As it is a constant commitment to digital sovereignity, data ownership and minimizing unnecessary dependencies on foreign cloud providers.
When Ghost published its 6.0 version, it introduced a native analytics feature that relies upon the New York, US-based Tinybird for cookie-free, real-time data processing. And by default does the documentation steer the admin of self-hosted Ghost instances (servers) to signup for an free Tinybird account for the cloud based offering. The free offering is good for small and medium blogs but does run on the cloud infrastructure of Google and(/or) Amazon.
Exactly this point feels for me like an compromise of the very principles that led for me to do self-hosting in the first place and therefore led to me to look for the possibility to pull that dependency into my own control.
The Experiment
My try to move to local Tinybird
Tinybird offers two ways to self-host its infrastructure. Tinybird Self-Managed Regions and Tinybird Local. The former one is aimed at enterprise-scale deployments with the related resources, whereas the latter is designed for development.
But with the right configuration and a robust reverse proxy can the Tinybird Local container function as a lightweight, permanent backend for Ghost Analytics on a self-hosted Ghost instance.
I tested the possibility of that in the night from the 6th to the 7th of June 2026. And as you see this version of the blog post I can say that this was successful – the analytics of this blog use the Tinybird Local container instead of the cloud offering.
The Architecture
What is running under the hood
We need to look at what the Tinybird Local is to understand how this works. This isn't a monolithic black box but a developer-centric streaming data platform build on top of ClickHouse – an database designed for analytics data.
When you spin up the tinybird-local container you are essentially starting a lightweight project model similar to like in the cloud offering of Tinybird but contained in a single container.
Here is how the data flows in this self-hosted ecosystem:
- The Reader: Someone visits the blog. Ghost's native tracking script sends an event.
- Caddy: Intercepts the analytics traffic and securely routes it to the local infrastructure.
- Tinybird Local: Ingests the raw event stream, validates it, and efficiently stores and aggregates the analytics data.
- Ghost Dashboard: Queries the local Tinybird API to display your real-time visitor stats.
If you like this blog post you can support my work by either becoming a paid member or leaving a tip.
The Setup
Making it Work
To transition your analytics backend from the cloud offering to an deployed instance of Tinybird Local we have to do the following (Requirement is to have Ghost already deployed via Docker):
Docker Compose
We can simply add the following configuration to the compose.yml with which we run Ghost:
tinybird-local:
image: tinybirdco/tinybird-local:latest
container_name: tinybird-local
restart: unless-stopped
ports:
- "127.0.0.1:7181:7181"
volumes:
- ./data/tb_local/clickhouse:/var/lib/clickhouse
- ./data/tb_local/redis:/redis-data
profiles: [analytics]
networks:
- proxy
healthcheck:
test: ["CMD", "curl", "-fsS", "http://localhost:7181"]
interval: 5s
timeout: 5s
retries: 30
tinybird-sync:
# Do not alter this without updating the Ghost container as well
image: ghost:${GHOST_VERSION}
container_name: tinybird-sync
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:
- ./data/tinybird/files:/data/tinybird
depends_on:
tinybird-local: # Add this
condition: service_healthy # Add this
tinybird-login:
condition: service_completed_successfully
networks:
- proxy
profiles: [analytics]
restart: no
tinybird-deploy:
build:
context: ./tinybird
dockerfile: Dockerfile
container_name: tinybird-deploy
working_dir: /data/tinybird
command: >
sh -c '
for i in $(seq 1 20); do
if tb-wrapper --cloud deploy -v; then
exit 0
fi
echo "Tinybird deploy not ready yet, retrying in 5s..."
sleep 5
done
exit 1
' # Change this
volumes:
- ./data/tinybird/home:/home/tinybird
- ./data/tinybird/files:/data/tinybird
depends_on:
tinybird-sync:
condition: service_completed_successfully
profiles: [analytics]
networks:
- proxy
tty: trueCaddy
Allowing Ghost to talk with the Tinybird Local container requires that we add the following to the Caddyfile. Replace DOMAIN with your own domain.
# Tinybird for Ghost
tinybird.DOMAIN {
@tinybird_admin {
path /tokens*
not remote_ip 127.0.0.1 ::1 100.64.0.0/10
}
respond @tinybird_admin "Forbidden" 403
reverse_proxy 127.0.0.1:7181
}Environment file
After starting the tinybird-local container for the first time can we look up the tokens via the http://localhost:7181/tokens URL and changing or adding the respective tokens to the .env file. Don't forget to also change the .tinyb file in the subdirectories of data/tinybird.
Closing thoughts
Securing your own digital sovereignty isn't always easy. It sometimes requires peeking behind or around official documentations and occasionally repurposing tools to serve production needs.
By pulling the Ghost Analytics of self-hosted instances out of the cloud do we eliminate a major point of external surveillance and take responsibility for the data privacy of the readers of our blog and members of our communities. It's just pure and high-power independent publishing.
Would you – if you have a Ghost based blog – switch your analytics backend or stay with whatever you have? Let's discuss! 📝
If you want to hear more from me you can find me in the Fediverse at @gelbphoenix@social.gelbphoenix.de (Mastodon) or @gelbphoenix@gram.social (Pixelfed). For more posts like this subscribe to my newsletter or support me by becoming a member or donating.