最近实在是受不了 Umami 用起来各种黏滞感,还是回到了 Plausible CE 的怀抱。

几年前我曾写过 简单、隐私友好的谷歌分析替代品,Plausible 自托管部署指南 ,随着新版本发布,有些内容已经过时了。最新的 自托管版本 部署起来更简单。此外,默认仍然是仅支持国家级的地理区域识别。当添加 MaxMind 集成后,则可以精确到城市级,这会增加 1GB 左右的 RAM 占用。

按照 官方 Wiki 配置后,我检查了容器日志,发现一直出现以下报错:

plausible-clickhouse  | 2025.11.28 03:06:51.659944 [ 1 ] {} <Warning> Context: Delay accounting is not enabled, OSIOWaitMicroseconds will not be gathered. You can ena
ble it using `echo 1 > /proc/sys/kernel/task_delayacct` or by using sysctl.
plausible     | Plausible.IngestRepo database already exists
plausible             | Creation of Db successful!
plausible             | Loading plausible..
plausible             | Starting dependencies..
plausible             | Starting repos..
plausible             | 03:07:02.357 [error] [locus] [geolocation] database failed to load (:remote): {:http, 451, ~c"Unavailable For Legal Reasons"}
plausible             | 03:07:03.664 [error] [locus] [geolocation] database failed to load (:remote): {:http, 451, ~c"Unavailable For Legal Reasons"}
plausible             | 03:07:05.515 [error] [locus] [geolocation] database failed to load (:remote): {:http, 451, ~c"Unavailable For Legal Reasons"}
plausible             | 03:07:08.199 [error] [locus] [geolocation] database failed to load (:remote): {:http, 451, ~c"Unavailable For Legal Reasons"}
plausible             | 03:07:12.181 [error] [locus] [geolocation] database failed to load (:remote): {:http, 451, ~c"Unavailable For Legal Reasons"}
plausible             | 03:07:18.135 [error] [locus] [geolocation] database failed to load (:remote): {:http, 451, ~c"Unavailable For Legal Reasons"}

我试着添加一个 geoipupdate 服务,好嘛,容器运行后同样也是 HTTP 451。这时我还以为是因为 MaxMind 把数据库下载地址迁移到 Cloudflare R2 后,之前的存储桶预签名 URL 出了问题。于是我尝试手动下载,结果出现了:

fuxk-maxmind.webp

去 Reddit 搜了下,一位自称 MaxMind 员工声称

他们不再提供免费 GeoLite 城市数据库,此限制仅适用于以下有限的国家或地区:中国/香港/澳门、古巴、伊朗、朝鲜、俄罗斯、委内瑞拉,访问 GeoLite 国家数据库的情况保持不变。

总之,当出现这种情况的时候,账号就已经被标记为 CN 了,除非换号。我尝试重新注册一个账号,然后因为 IP 不干净注册又失败了😅。说实话,这时候已经有点冒火了。(没错,就是无能狂怒~)

pussy-maxmind.webp

之前我在 GitHub 上提了 Issue #262 ,得到项目维护者回复,在新版本中,我仍可以通过手动下载可用的 GeoIP 数据库,映射到容器内部使用。

正好又发现一个 GitHub Action 定期发布 GeoLite 的存储库,由 P3TERX 大佬维护,我们从这里下载。开始前,做点准备工作。

# Plausible CE compose.yml template dir
pwd

# like this
/path/to/container/plausible

# change directory to above dir
cd /path/to/container/plausible

# for bash shell logs
mkdir -p geoip/logs

让 Gemini 协助,写了一个 Bash 脚本 update_geoip.sh

#!/bin/bash

DOWNLOAD_URL="https://github.com/P3TERX/GeoLite.mmdb/releases/latest/download/GeoLite2-City.mmdb"

# Plausible CE compose.yml template dir
PLAUSIBLE_DIR="/path/to/container/plausible" 

# file user uid/gid
HOST_UID=1000
HOST_GID=1000

# target dir
TARGET_DIR="${PLAUSIBLE_DIR}/geoip"
TARGET_FILE="${TARGET_DIR}/city.mmdb"
TEMP_FILE="${TARGET_FILE}.tmp"
LOG_DIR="${TARGET_DIR}/logs"

COMPOSE_DIR="${PLAUSIBLE_DIR}"

# log format
LOG_FILE="${LOG_DIR}/$(date +%Y%m%d).log"


# check target dir
if [ ! -d "$TARGET_DIR" ]; then
    echo "$(date '+%Y-%m-%d %H:%M:%S') - Error: Target directory '$TARGET_DIR' does not exist. Please create it first." | tee -a "$LOG_FILE"
    exit 1
fi

# mk log dir
mkdir -p "$LOG_DIR"
echo "$(date '+%Y-%m-%d %H:%M:%S') - Script started. Attempting to download GeoIP DB..." | tee -a "$LOG_FILE"


# --- download  logic---
echo "$(date '+%Y-%m-%d %H:%M:%S') - Downloading to temporary file $TEMP_FILE..." | tee -a "$LOG_FILE"

if curl -L --fail "$DOWNLOAD_URL" -o "$TEMP_FILE"; then
    echo "$(date '+%Y-%m-%d %H:%M:%S') - Download successful. Setting ownership and permissions..." | tee -a "$LOG_FILE"

    # set file permission to UID/GID 1000
    if chown ${HOST_UID}:${HOST_GID} "$TEMP_FILE"; then
        echo "$(date '+%Y-%m-%d %H:%M:%S') - Ownership set to ${HOST_UID}:${HOST_GID} for $TEMP_FILE." | tee -a "$LOG_FILE"
    else
        echo "$(date '+%Y-%m-%d %H:%M:%S') - Error: Failed to change ownership for $TEMP_FILE. Exiting." | tee -a "$LOG_FILE"
        # rm temp file
        rm -f "$TEMP_FILE"
        exit 1
    fi

    if chmod 644 "$TEMP_FILE"; then
        mv "$TEMP_FILE" "$TARGET_FILE"
        echo "$(date '+%Y-%m-%d %H:%M:%S') - File updated and permissions set for $TARGET_FILE." | tee -a "$LOG_FILE"
    else
        echo "$(date '+%Y-%m-%d %H:%M:%S') - Error: Failed to set permissions for $TEMP_FILE. Exiting." | tee -a "$LOG_FILE"
        rm -f "$TEMP_FILE" 
        exit 1
    fi
    
    # --- restart container ---
    if ! cd "$COMPOSE_DIR"; then
        echo "$(date '+%Y-%m-%d %H:%M:%S') - Error: Failed to change directory to $COMPOSE_DIR." | tee -a "$LOG_FILE"
        exit 1
    fi

    echo "$(date '+%Y-%m-%d %H:%M:%S') - Stopping and removing containers (docker compose down)..." | tee -a "$LOG_FILE"
    if docker compose down; then
        echo "$(date '+%Y-%m-%d %H:%M:%S') - Containers stopped successfully. Starting them up again..." | tee -a "$LOG_FILE"
        
        if docker compose up -d; then
            echo "$(date '+%Y-%m-%d %H:%M:%S') - Containers started successfully." | tee -a "$LOG_FILE"
        else
            echo "$(date '+%Y-%m-%d %H:%M:%S') - Error: Failed to start containers (docker compose up -d). Check permissions." | tee -a "$LOG_FILE"
            exit 1 
        fi
    else
        echo "$(date '+%Y-%m-%d %H:%M:%S') - Error: Failed to stop containers (docker compose down). Check permissions." | tee -a "$LOG_FILE"
        exit 1 
    fi

else
    echo "$(date '+%Y-%m-%d %H:%M:%S') - Error: Failed to download GeoLite2-City.mmdb. Curl exit code: $?." | tee -a "$LOG_FILE"
    rm -f "$TEMP_FILE" 
    exit 1
fi

echo "$(date '+%Y-%m-%d %H:%M:%S') - Script finished successfully." | tee -a "$LOG_FILE"
exit 0

修改实际的路径以及合理的用户/组 ID(通常首个普通用户是 1000/1000,可使用 id 命令确认),保存后赋予脚本可执行权限

sudo chmod +x update-geoip.sh

尝试首次运行

sudo ./update-geoip.sh
2025-11-29 05:54:43 - Downloading GeoLite2-City.mmdb from https://github.com/P3TERX/GeoLite.mmdb/releases/latest/download/GeoLite2-City.mmdb...
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
100 60.2M  100 60.2M    0     0  53.1M      0  0:00:01  0:00:01 --:--:-- 53.1M
2025-11-29 05:54:45 - Download successful. File saved to /path/to/container/plausible/geoip/city.mmdb
2025-11-29 05:54:45 - Setting file permissions to 644 for /path/to/container/plausible/geoip/city.mmdb...
2025-11-29 05:54:45 - File permissions set for /path/to/container/plausible/geoip/city.mmdb.

修改 docker-compose.yml,找到这一部分

  plausible:
    image: ghcr.io/plausible/community-edition:v3.1.0
    container_name: plausible
    restart: unless-stopped
    command: sh -c "/entrypoint.sh db createdb && /entrypoint.sh db migrate && /entrypoint.sh run"
    depends_on:
      plausible_db:
        condition: service_healthy
      plausible_events_db:
        condition: service_healthy
    volumes:
      - ./plausible-data:/var/lib/plausible

添加一个 Docker 数据卷映射

volumes:
  - ./plausible-data:/var/lib/plausible
+ - ./geoip/city.mmdb:/var/lib/plausible-mmdb/city.mmdb:ro

需要在 .env 里添加一个环境变量

IP_GEOLOCATION_DB=/var/lib/plausible-mmdb/city.mmdb

重启容器

sudo docker compose down && sudo docker compose up -d && sudo docker compose logs -f

打开 Plausible CE 控制台,切换到实时访客视图

realtime-view.webp

可以看到区域和城市了

realtime-geo.webp

定期手动执行该脚本更新 GeoIP 数据库即可,或者,我们设置一个 Crontab 定时任务。考虑到对更新需求没那么大,我选择在 GitHub Actions 发布的两个周期左右,延迟几小时执行

sudo crontab -e

在最后添加一行(UTC 时间)

0 4 1,7,13,19,25 * * /bin/bash /path/to/container/plausible/update_geoip.sh

嗯,现在应该完全工作了。