In this guide I want to go over how I Was able to deploy Rybbit: the self-hosted analytics dashboard. This has been a major improvement in how I track and monitor viewership of my site. as well as who is connecting to my sites.
I use Rybbit to track all of my Next JS sites, and so far it has been working great. However as Rybbit has recently upgraded to version 2.4.0 I wanted to showcase how I achieved this, since others including myself at first had difficulty getting it all setup and working properly.
Where the Challenged began:
Originally I was having issues with being able to actually create an organization. I saw 404’s in the request logs for anything that was pointed to /api , so I concluded that my reverse proxy must be to blame. It was not. So I was troubleshooting the routes the project took and noticed that only within very recent versions did some api endpoints begin with /1/api rather than /api so my custom Pangolin regex re-write to /api wouldn’t work correctly.
Where I am a giant fucking dumbass
I mistakenly decided to worry about generating my production credentials after getting setup. This is wrong. Do not do this. The part where it wants you to generate a 32 character string is required to be able to create organizations. I assumed I could at least demo it out without making full time credentials but a quick generation of a 32 character string using openssl and suddenly I was able to log in, and create organizations.
There is an official YouTube video from Pangolin on how to deploy this, and what I used as a reference while going through it.
https://www.youtube.com/watch?v=zk-ZhkNdyC8
Working Configuration
Here is my environment variables
DOMAIN_NAME=tracking.mydomain.me
BASE_URL=https://tracking.mydomain.me
BETTER_AUTH_SECRET=THISNEEDSTOBEA32CHARATERSTRINGVERYIMPORTANT
USE_WEBSERVER=false
CLICKHOUSE_DB=analytics
CLICKHOUSE_USER=default
CLICKHOUSE_PASSWORD=frog
POSTGRES_DB=analytics
POSTGRES_USER=frog
POSTGRES_PASSWORD=frog
HOST_BACKEND_PORT=3001:3001 # pangolin needs it like this
HOST_CLIENT_PORT=3002:3002 # pangolin needs it like this For this do not worry if you already run services on ports 3001/3002 , this is only for the internal docker network, and not your host network, as you will see in the docker-compose.yml there is a newt we install within the docker network to give us access in
Here is the docker-compose.yml used.
services:
newt:
image: fosrl/newt
container_name: newt-rybbit
restart: unless-stopped
environment:
- PANGOLIN_ENDPOINT=https://pangolin.mydomain.me
- NEWT_ID=newtID
- NEWT_SECRET=newtsecret
clickhouse:
container_name: clickhouse
image: clickhouse/clickhouse-server:25.4.2
volumes:
- clickhouse-data:/var/lib/clickhouse
configs:
- source: clickhouse_network
target: /etc/clickhouse-server/config.d/network.xml
- source: clickhouse_json
target: /etc/clickhouse-server/config.d/enable_json.xml
- source: clickhouse_logging
target: /etc/clickhouse-server/config.d/logging_rules.xml
- source: clickhouse_user_logging
target: /etc/clickhouse-server/config.d/user_logging.xml
environment:
- CLICKHOUSE_DB=${CLICKHOUSE_DB:-analytics}
- CLICKHOUSE_USER=${CLICKHOUSE_USER:-default}
- CLICKHOUSE_PASSWORD=${CLICKHOUSE_PASSWORD:-frog}
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8123/ping"]
interval: 3s
timeout: 5s
retries: 5
start_period: 10s
restart: unless-stopped
ports:
- "8123:8123"
postgres:
image: postgres:17.4
container_name: postgres
environment:
- POSTGRES_USER=${POSTGRES_USER:-frog}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-frog}
- POSTGRES_DB=${POSTGRES_DB:-analytics}
volumes:
- postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 3s
timeout: 5s
retries: 5
start_period: 10s
restart: unless-stopped
ports:
- "5432:5432"
backend:
image: ghcr.io/rybbit-io/rybbit-backend:${IMAGE_TAG:-latest}
container_name: backend
ports:
- "${HOST_BACKEND_PORT:-127.0.0.1:3001:3001}"
environment:
- NODE_ENV=production
- CLICKHOUSE_HOST=http://clickhouse:8123
- CLICKHOUSE_DB=${CLICKHOUSE_DB:-analytics}
- CLICKHOUSE_PASSWORD=${CLICKHOUSE_PASSWORD:-frog}
- POSTGRES_HOST=postgres
- POSTGRES_PORT=5432
- POSTGRES_DB=${POSTGRES_DB:-analytics}
- POSTGRES_USER=${POSTGRES_USER:-frog}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-frog}
- BETTER_AUTH_SECRET=${BETTER_AUTH_SECRET}
- BASE_URL=${BASE_URL}
- DISABLE_SIGNUP=${DISABLE_SIGNUP}
- DISABLE_TELEMETRY=${DISABLE_TELEMETRY}
- MAPBOX_TOKEN=${MAPBOX_TOKEN}
depends_on:
clickhouse:
condition: service_healthy
postgres:
condition: service_started
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:3001/api/health"]
interval: 3s
timeout: 5s
retries: 5
start_period: 10s
restart: unless-stopped
client:
image: ghcr.io/rybbit-io/rybbit-client:${IMAGE_TAG:-latest}
container_name: client
ports:
- "${HOST_CLIENT_PORT:-127.0.0.1:3002:3002}"
environment:
- NODE_ENV=production
- NEXT_PUBLIC_BACKEND_URL=${BASE_URL}
- NEXT_PUBLIC_DISABLE_SIGNUP=${DISABLE_SIGNUP}
depends_on:
- backend
restart: unless-stopped
volumes:
clickhouse-data:
postgres-data:
caddy_data:
caddy_config:
configs:
clickhouse_network:
content: |
<clickhouse>
<listen_host>0.0.0.0</listen_host>
</clickhouse>
clickhouse_json:
content: |
<clickhouse>
<settings>
<enable_json_type>1</enable_json_type>
</settings>
</clickhouse>
clickhouse_logging:
content: |
<clickhouse>
<logger>
<level>warning</level>
<console>true</console>
</logger>
<query_thread_log remove="remove"/>
<query_log remove="remove"/>
<text_log remove="remove"/>
<trace_log remove="remove"/>
<metric_log remove="remove"/>
<asynchronous_metric_log remove="remove"/>
<session_log remove="remove"/>
<part_log remove="remove"/>
<latency_log remove="remove"/>
<processors_profile_log remove="remove"/>
</clickhouse>
clickhouse_user_logging:
content: |
<clickhouse>
<profiles>
<default>
<log_queries>0</log_queries>
<log_query_threads>0</log_query_threads>
<log_processors_profiles>0</log_processors_profiles>
</default>
</profiles>
</clickhouse>Important things to Note
Notice the hostname's of the docker containers for backend and client, we will address our docker containers by their hostname in the Pangolin configuration
Pangolin Configuration
Here is what needs to be set on Pangolin. Here take note of the lack of “Advanced Mode” this is actually not needed, I use it just fine with it disabled. What is important however is the Headers. Notice the HTTPS x-forwarded-for and the x-forwarded-host which is what allowed me to stop getting errors when communicating to the back end.
Wrapping up:
So you just need to follow the docker-compose along with the Pangolin config I shared, and you should be good, but if it doesn’t work I honestly do not know what to tell you, This was a pain getting setup, but I think the headers is really what solved the issues with back end communication. I have tried all the approaches using nginx-proxy manager, whatever else is recommended when self hosting and it all did not work. I also wish Pangolin themselves provided written documentation into how to do this.

