---
title: "How I was able to deploy Rybbit: self-hosted Google Analytics replacement with Pangolin: self hosted reverse proxy"
url: https://bee.whoisjason.me/blogposts/cmlsh1ggd0002mhr1zun2940e
author: Jason
published: 2026-02-18
topics: ["linux", "networking", "docker"]
reading_time_minutes: 4
word_count: 796
---
# How I was able to deploy Rybbit: self-hosted Google Analytics replacement with Pangolin: self hosted reverse proxy

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](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.
