Pi-hole Docker Compose 部署指南

Pi-hole v6.5(镜像 2026.10.0)Docker Compose 从零部署指南:端口规划、compose 配置逐项说明、Web 登录/DNS 解析/拦截/Local DNS 全流程实测验证与常见问题排障

Pi-holePi-holeDockerDNS2026/10/18

Pi-hole Docker Compose 部署指南

目标:在一台 Ubuntu 22.04 服务器上用 Docker Compose 从零部署 Pi-hole v6.5(镜像 pihole/pihole:2026.10.0),完成 Web 管理台登录、DNS 解析、广告拦截、Local DNS 自定义记录的全流程实测验证。已在 Ubuntu 22.04 实测通过。

一、环境信息

本篇所有命令与回显均在以下环境实测取得,读者按相同软件版本操作可获得一致结果。

  • 操作系统:Ubuntu 22.04(x86_64)
  • Docker:29.8.1(含 Compose v2 插件)
  • Pi-hole 镜像:pihole/pihole:2026.10.0(2026-10-17 推送,对应 Core v6.5 / Web v6.6.1 / FTL v6.8,镜像 label 实查 org.opencontainers.image.version=2026.10.0)
  • 端口规划:宿主 8091 → Web 80(宿主 80 已被其他服务占用);宿主 5330 → DNS 53(宿主 53 已被 systemd-resolved 占用监听 127.0.0.53)
  • 自定义网络:10.89.21.0/24(部署前已复核未被占用)
  • 数据目录:/opt/data/pihole/etc-pihole(挂载到实例内 /etc/pihole)
  • 管理账号:admin(Web 密码 q1w2e3r4,通过环境变量设置)
  • 时区:Asia/Shanghai

二、镜像选型说明

Pi-hole 官方维护的打包运行时项目是 docker-pi-hole(GitHub 11.6k stars),镜像发布在 Docker Hub pihole/pihole。选镜像前先确认 tag 与版本对应关系(2026-10-18 实查 Docker Hub tags API):

tag推送日期说明
latest2026-10-17当前稳定版滚动 tag,与 2026.10.0 相同
2026.10.02026-10-17本次实测使用的固定版本 tag(对应核心 v6.5)
2026.09.02026-09-19上一版
nightly2026-10-18每夜构建,仅测试用

关键差异与如何选型:生产环境固定使用日期式版本 tag(如 2026.10.0),不要用 latest——升级时机自己掌控,回滚也有明确锚点;nightly 只适合复现问题。

版本对应关系核查方法(镜像 label 直接写明版本):

docker inspect pihole/pihole:2026.10.0 --format '{{index .Config.Labels "org.opencontainers.image.version"}}'

实测回显:

2026.10.0

💡 说明:Pi-hole 套件版本由三个组件组成(Core/Web/FTL),镜像 tag 是打包日期式版本。进入实例后执行 pihole version 可看三件套实际版本,本镜像实测输出:

Core version is v6.5 (Latest: v6.5)
Web version is v6.6.1 (Latest: v6.6.1)
FTL version is v6.8 (Latest: v6.8)

三、前置条件

部署前逐项确认三件事:端口空闲、网络网段可用、目录规划好。

3.1 端口复核

Pi-hole 要占用 Web 端口(默认 80)与 DNS 端口 53(TCP+UDP)。Ubuntu 22.04 默认由 systemd-resolved 在 127.0.0.53:53 提供本地存根解析,宿主 53 实际不可用,因此本篇 DNS 映射到 5330;宿主 80 已被其他服务占用,Web 映射到 8091。部署前先复核目标端口空闲:

ss -ltn | grep -E ':(8091|5330)\b'

实测回显(无输出说明 8091/5330 均空闲):

⚠️ 注意:ss 无输出才是「空闲」。若目标端口已被占用,顺延换一个空闲端口即可,Compose 文件与防火墙同步修改。

3.2 网络网段复核

为避免与物理网段冲突(自建网络若落入 192.168.0.0/16 可能抢走路由导致整机断网),显式指定 10.89.21.0/24 网段,创建前复核未被占用:

docker network ls --format '{{.Name}}' | while read n; do
  docker network inspect "$n" --format '{{range .IPAM.Config}}{{.Subnet}} {{end}}'
done | grep -q '10.89.21.0/24' && echo '网段已被占用' || echo '10.89.21.0/24 可用'

实测回显:

10.89.21.0/24 可用

3.3 目录规划

mkdir -p /opt/data/pihole/etc-pihole
cd /opt/data/pihole

实测回显(mkdir 无输出即成功,确认目录已就位):

ls -ld /opt/data/pihole/etc-pihole
drwxr-xr-x 2 root root 4096 Oct 18 15:09 /opt/data/pihole/etc-pihole

四、部署步骤

本章从编写 Compose 文件到服务可用验证,共 5 步,每步都有实测回显。

4.1 编写 docker-compose.yml

在 /opt/data/pihole/docker-compose.yml 写入以下内容:

services:
  pihole:
    image: pihole/pihole:2026.10.0
    container_name: pihole-v6-web
    hostname: pihole
    restart: unless-stopped
    ports:
      - "8091:80/tcp"          # Web 管理台(宿主 80 已占用,改 8091)
      - "5330:53/tcp"          # DNS over TCP(宿主 53 被 systemd-resolved 占用)
      - "5330:53/udp"          # DNS over UDP
    environment:
      TZ: "Asia/Shanghai"                       # 时区
      FTLCONF_webserver_api_password: "q1w2e3r4" # Web 管理密码
      FTLCONF_dns_upstreams: "223.5.5.5;119.29.29.29"  # 上游 DNS(分号分隔多个)
    volumes:
      - ./etc-pihole:/etc/pihole                # 配置与数据持久化
    cap_add:
      - NET_ADMIN                               # DHCP/NTP 功能需要
    networks:
      default:
        ipv4_address: 10.89.21.10               # 固定实例内网 IP

networks:
  default:
    ipam:
      config:
        - subnet: 10.89.21.0/24                 # 显式网段,禁止依赖自动分配

配置逐项说明:

配置项取值与作用
image固定版本 tag 2026.10.0,升级回滚都有锚点
container_name实例名固定为 pihole-v6-web,便于日志与 exec 操作
ports(8091:80)Web 管理台映射,避开宿主已占用的 80
ports(5330:53 tcp+udp)DNS 必须同时映射 TCP 与 UDP;宿主 53 被 systemd-resolved 占用(监听 127.0.0.53),故用 5330
FTLCONF_webserver_api_passwordv6 环境变量注入 Web 密码,免去安装向导
FTLCONF_dns_upstreams上游 DNS 数组,环境变量用分号分隔;也可后续在 Web 台改
volumes/etc/pihole 存放 pihole.toml、gravity.db 等全部数据,必须持久化
cap_add: NET_ADMIN启用 DHCP/NTP 能力所需;仅做 DNS 拦截可省,官方默认保留
networks显式 10.89.21.0/24 网段 + 固定 IP,避免自动分配撞网段

⚠️ 注意:compose 的 networks.default.ipam.config.subnet 必须显式写。Docker 自动分配网段可能落到与物理网络冲突的 192.168.0.0/16 段,导致宿主机路由被劫持、整机断网。

4.2 启动服务

cd /opt/data/pihole && docker compose up -d

实测回显:

 Network pihole_default Created
 Container pihole-v6-web Creating
 Container pihole-v6-web Created
 Container pihole-v6-web Starting
 Container pihole-v6-web Started

(实测目录名为部署工作目录名,上例按 /opt/data/pihole 部署时网络名即为 pihole_default。)

确认实例状态:

docker compose ps

实测回显:

NAME            IMAGE                     COMMAND      SERVICE   CREATED                  STATUS                                     PORTS
pihole-v6-web   pihole/pihole:2026.10.0   "start.sh"   pihole    Less than a second ago   Up Less than a second (health: starting)   67/udp, 123/udp, 443/tcp, 0.0.0.0:5330->53/tcp, 0.0.0.0:5330->53/udp, [::]:5330->53/tcp, [::]:5330->53/udp, 0.0.0.0:8091->80/tcp, [::]:8091->80/tcp

关键解读:PORTS 列确认三组映射全部生效(5330 tcp/udp → 53、8091 → 80);STATUS 中 health: starting 表示内置健康检查启动中,首次启动要等 gravity 初始化(拉取默认拦截列表),约 1–2 分钟。

4.3 验证网络与路由安全

确认自建网段与宿主路由未受影响:

docker network inspect pihole_default --format '{{range .IPAM.Config}}{{.Subnet}}{{end}}'

实测回显:

10.89.21.0/24
ip route get 192.168.1.62

实测回显(仍走物理网卡 ens33,未被网桥劫持):

192.168.1.62 via 192.168.146.2 dev ens33 src 192.168.146.128 uid 0
    cache

4.4 观察首次启动日志(gravity 初始化)

首次启动时实例自动执行 gravity 初始化:拉取默认拦截列表 StevenBlack hosts 并建库。观察日志:

docker logs pihole-v6-web 2>&1 | tail -40

实测回显:

  [i] Neutrino emissions detected...

  [✓] Preparing new gravity database
  [✓] Creating new gravity databases
  [✓] Pulling blocklist source list into range
  [i] Using libz compression

  [i] Target: https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts
  [✓] Status: Retrieval successful
  [✓] Parsed 73634 exact domains and 0 ABP-style domains (blocking, ignored 1 non-domain entries)
      Sample of non-domain entries:
        - fe80::1%lo0

  [✓] Building gravity tree
  [✓] Building antigravity tree

  [i] Number of gravity domains: 73634 (73634 unique domains)
  [i] Number of antigravity domains: 0 (0 unique domains)
  [i] Number of exact denied domains: 0
  [i] Number of regex denied filters: 0
  [i] Number of exact allowed domains: 0
  [i] Number of regex allowed filters: 0

  [✓] Optimizing database
  [✓] Swapping databases
  [✓] The old database remains available
  [✓] Cleaning up stray matter

  [✓] Done.

  [i] pihole-FTL pre-start checks
  [i] Setting capabilities on pihole-FTL where possible
  [i] Applying the following caps to pihole-FTL:
        * CAP_CHOWN
        * CAP_NET_BIND_SERVICE
        * CAP_NET_RAW
        * CAP_NET_ADMIN

  [i] Starting pihole-FTL (no-daemon) as pihole

关键解读:[✓] Done. 表示 gravity 数据库就绪(默认列表解析出 73634 个域名);Starting pihole-FTL (no-daemon) 表示 DNS 引擎已前台启动。看到这两行即可开始功能验证。

4.5 服务可用性验证

Web 管理台:确认 HTTP 服务已监听(未登录访问 /admin/ 会 302 跳转到登录页):

curl -s -o /dev/null -w 'HTTP %{http_code}\n' http://127.0.0.1:8091/admin/

实测回显:

HTTP 302

API 密码登录(环境变量设置的密码 q1w2e3r4 生效验证):

curl -s -X POST http://127.0.0.1:8091/api/auth -H 'Content-Type: application/json' -d '{"password":"q1w2e3r4"}'

实测回显:

{
    "session": {
        "valid": true,
        "totp": false,
        "sid": "l7ntcyn+BmfIKrfbAG7bmA=",
        "csrf": "/WuEgSMzZAHm9UaUi/vcvA=",
        "validity": 1800,
        "message": "password correct"
    },
    "took": 0.03418898582458496
}

关键解读:valid: true + message: password correct 表示 Web 密码已生效;sid 是后续 REST API 调用的会话令牌(有效期 1800 秒),浏览器登录 Web 台用的是同一套密码。

DNS 解析(宿主 5330 端口):

dig @127.0.0.1 -p 5330 www.baidu.com +short

实测回显:

www.a.shifen.com.
110.242.70.57
110.242.69.21

TCP/UDP 双栈验证(DNS 端口两个协议都要通):

dig @127.0.0.1 -p 5330 www.baidu.com +tcp +short

实测回显:

www.a.shifen.com.
110.242.70.57
110.242.69.21

💡 说明:dig 默认走 UDP;+tcp 显式走 TCP。两者都返回结果说明 TCP/UDP 双栈映射正确。

至此部署完成:Web 台地址 http://<服务器IP>:8091/admin/,局域网设备 DNS 指向 <服务器IP>:5330 即可开始全网拦截。

4.6 功能实测:拦截与 Local DNS(部署验收)

本节是部署后的验收实测:验证拦截规则添加生效、Local DNS 自定义记录生效。先通过 REST API 登录拿会话令牌(上节已验证),再添加一条精确拦截:

SID=$(curl -s -X POST http://127.0.0.1:8091/api/auth -H 'Content-Type: application/json' -d '{"password":"q1w2e3r4"}' | python3 -c "import json,sys; print(json.load(sys.stdin)['session']['sid'])")
curl -s -X POST "http://127.0.0.1:8091/api/domains/deny/exact" -H "X-FTL-SID: $SID" -H 'Content-Type: application/json' -d '{"domain":"ads.example-portal.com","comment":"广告拦截测试"}'

实测回显:

{
    "domains": [
        {
            "domain": "ads.example-portal.com",
            "unicode": "ads.example-portal.com",
            "type": "deny",
            "kind": "exact",
            "comment": "广告拦截测试",
            "groups": [0],
            "enabled": true,
            "id": 1,
            "date_added": 1792307524,
            "date_modified": 1792307524
        }
    ],
    "processed": {
        "errors": [],
        "success": [{"item": "ads.example-portal.com"}]
    },
    "took": 0.002268552780151367
}

⚠️ 注意:v6 API 路径是 /api/domains/deny/exact(deny 在前),写成 /api/domains/exact/deny 会报 400 Invalid request: Specify list to modify more precisely——这是 v6 相对文档早期草稿最容易踩的坑(实测踩过)。

dig 验证拦截(该域名不在默认列表,添加前查询返回 NXDOMAIN 空结果,添加后返回 0.0.0.0):

dig @127.0.0.1 -p 5330 ads.example-portal.com | sed -n '/flags:/p;/ANSWER SECTION/,/^$/p'

实测回显:

;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; ANSWER SECTION:
ads.example-portal.com. 2       IN      A       0.0.0.0

⚠️ 注意:添加后立即查询可能返回 NXDOMAIN(拦截规则尚未同步到引擎内存),间隔 1–2 秒再查即为 0.0.0.0。属于状态切换时序,不是配置失败。

Local DNS 自定义记录(内网域名映射),用 PATCH 接口写 dns.hosts(格式为 "IP 主机名",与 hosts 文件一致):

curl -s -X PATCH http://127.0.0.1:8091/api/config/dns/hosts -H "X-FTL-SID: $SID" -H 'Content-Type: application/json' -d '{"config":{"dns":{"hosts":["10.89.21.50 mydev.lan"]}}}'

实测回显(节选):

{"config": {"dns": {"hosts": ["10.89.21.50 mydev.lan"]}}, "took": 0.0217}

dig 验证:

dig @127.0.0.1 -p 5330 mydev.lan +short

实测回显:

10.89.21.50

五、数据持久化说明

Pi-hole 的全部持久化数据都在实例内 /etc/pihole 目录,已通过 compose 挂载到宿主 /opt/data/pihole/etc-pihole。目录内容与作用:

文件/目录作用
pihole.toml全量配置(v6 唯一配置源),Web 台/API/CLI 的修改最终都写回这里
gravity.db拦截库(adlist 源、黑白名单、分组)
pihole-FTL.db查询历史与统计数据
gravity_old.db 等gravity 更新时保留的旧库,便于回退

实例内确认(部署并完成一轮配置修改后查看,宿主持久化目录 /opt/data/pihole/etc-pihole 内容与之相同):

docker exec pihole-v6-web ls -la /etc/pihole/ | head -20

实测回显:

total 9308
drwxr-xr-x 7 pihole pihole    4096 Oct 18 15:16 .
drwxr-xr-x 1 root   root      4096 Oct 18 15:10 ..
-rw-r----- 1 pihole pihole      65 Oct 18 15:16 adlists.list
-rw-r----- 1 pihole pihole      44 Oct 18 15:16 cli_pw
drwxr-xr-x 2 pihole pihole    4096 Oct 18 15:15 config_backups
-rw-r--r-- 1 pihole pihole       0 Oct 18 15:16 dhcp.leases
-rw-r----- 1 pihole pihole    5875 Oct 18 15:10 dnsmasq.conf
-rw-r----- 1 pihole pihole 4796416 Oct 18 15:15 gravity.db
drwxr-xr-x 2 pihole pihole    4096 Oct 18 15:15 gravity_backups
-rw-r----- 1 pihole pihole 4386816 Oct 18 15:15 gravity_old.db
drwxr-xr-x 2 pihole pihole    4096 Oct 18 15:13 hosts
drwxr-xr-x 2 pihole pihole    4096 Oct 18 15:15 listsCache
drwxr-xr-x 2 pihole pihole    4096 Oct 18 15:10 migration_backup
-rw-r----- 1 pihole pihole   90112 Oct 18 15:16 pihole-FTL.db
-rw-r----- 1 pihole pihole   32768 Oct 18 15:25 pihole-FTL.db-shm
-rw-r----- 1 pihole pihole   74192 Oct 18 15:25 pihole-FTL.db-wal
-rw-r----- 1 pihole pihole   71585 Oct 18 15:15 pihole.toml
-rw-r----- 1 pihole pihole       0 Oct 18 15:10 pihole.toml.lock
-rw------- 1 pihole pihole     709 Oct 18 15:10 tls.crt

重启持久化实测:重启服务后配置(Local DNS 记录 mydev.lan=10.89.21.50,见 4.6 节实测)与拦截规则都必须还在:

cd /opt/data/pihole && docker compose restart && sleep 8
dig @127.0.0.1 -p 5330 mydev.lan +short
dig @127.0.0.1 -p 5330 doubleclick.net +short
docker compose ps --format '{{.Name}} {{.Status}}'

实测回显:

 Container pihole-v6-web Restarting
 Container pihole-v6-web Started
10.89.21.50
0.0.0.0
pihole-v6-web Up 8 seconds (health: starting)

关键解读:重启后自定义记录 mydev.lan 仍解析为 10.89.21.50、拦截域名仍返回 0.0.0.0,证明数据全部落在宿主持久化目录,实例重建也不丢。

⚠️ 注意:官方镜像内置每周日凌晨自动更新拦截列表的 cron,列表数据无需自己加定时任务;但版本升级要靠更新镜像 tag 后重建实例,数据在挂载目录中自然延续。

六、环境变量表

Pi-hole v6 打包运行时的环境变量规则:任意 pihole.toml 配置项都可映射为 FTLCONF_ + 配置路径(toml 的点号改为下划线),实例启动时写入配置并在文件中标注 ### CHANGED (env)。本篇实测用到的与官方示例中的常用变量:

环境变量示例值作用对应 toml 项
TZAsia/Shanghai系统时区(日志与统计时间轴)系统变量
FTLCONF_webserver_api_passwordq1w2e3r4Web 管理台密码(不设则随机生成)webserver.api.password
FTLCONF_dns_upstreams223.5.5.5;119.29.29.29上游 DNS 列表,分号分隔dns.upstreams
FTLCONF_dns_listeningModeALLDNS 监听模式;官方建议默认 bridge 网络下设 ALL(实测未设时从宿主映射口查询也正常,因映射后源地址仍属私有网段)dns.listeningMode
FTLCONF_misc_etc_dnsmasq_dtrue启用 /etc/dnsmasq.d 自定义配置目录(v5 迁移场景用)misc.etc_dnsmasq_d

关键解读:数组型配置(如 dns.upstreams)用分号分隔;密码类值建议用 compose 的 environment 明文写(内网受控环境可接受),更敏感的场景用 env_file 引入环境文件并配合 .gitignore 管理。

上游环境变量生效验证(toml 中标注 CHANGED (env)):

docker exec pihole-v6-web grep -B2 -A5 'upstreams = ' /etc/pihole/pihole.toml | tail -8

实测回显:

  upstreams = [
    "223.5.5.5",
    "119.29.29.29"
  ] ### CHANGED (env), default = []

七、配置参数说明

v6 的全部配置收敛在 /etc/pihole/pihole.toml(实测 1772 行,带逐项中文注释),环境变量与 Web 台修改最终都落到此文件。部署后重点核对以下四段(可在实例内 grep 定位):

docker exec pihole-v6-web grep -E '^(dns|webserver|blocking|misc) ' /etc/pihole/pihole.toml

实测回显(toml 顶层分组行):

[dns]
[webserver]
[blocking]
[misc]
配置组核心键说明
[dns]upstreams、listeningMode、hosts、cache上游、监听范围、Local DNS 记录、缓存
[webserver]port、api.password、session.timeoutWeb 台端口/密码/会话时长(实测登录会话 validity=1800 秒)
[blocking]mode(默认 NULL)、edns0ECS拦截模式,NULL 即拦截返回 0.0.0.0
[misc]nice、privacylevel、check.load进程优先级、隐私级别、负载自检

配置修改三种途径(效果等价,都写回 toml):

  1. Web 管理台 Settings 页(可视化);
  2. REST API PATCH(自动化);
  3. 直接编辑宿主持久化目录中的 pihole.toml 后重启实例(docker compose restart)。

💡 说明:全文级参数详解(含 dns/webserver/api/blocking 各段逐项说明与生产推荐值)见本专栏《Pi-hole v6.5 配置参数详解》篇。

八、配置文件挂载

除主挂载 /etc/pihole 外,可能用到的其他挂载点与场景:

挂载场景是否必须
./etc-pihole:/etc/pihole配置与数据持久化(toml/gravity.db/FTL.db)必须
./etc-dnsmasq.d:/etc/dnsmasq.d自定义 dnsmasq 配置;v6 起多数场景不需要,需同时设 FTLCONF_misc_etc_dnsmasq_d: 'true'可选
./logs:/var/log/pihole把 DNS 查询日志(pihole.log)、引擎日志(FTL.log)持久化到宿主可选(本篇未挂载,日志在实例内查看)

挂载生效验证(/var/log/pihole 在实例内的实际文件,验证日志位置):

docker exec pihole-v6-web ls -la /var/log/pihole/

实测回显:

total 44
drwxr-xr-x 1 pihole pihole 4096 Oct 18 15:10 .
drwxr-xr-x 1 root   root   4096 Oct 17 19:55 ..
-rw-r----- 1 pihole pihole 9143 Oct 18 15:15 FTL.log
-rw-r----- 1 pihole pihole 9151 Oct 18 15:15 pihole.log
-rw-r--r-- 1 pihole pihole  100 Oct 18 15:10 webserver.log

关键解读:pihole.log 是 DNS 查询日志(审计用)、FTL.log 是引擎运行日志(排障用)、webserver.log 是 Web 台日志。若要日志随实例生命周期持久保留,在 compose 增加 ./logs:/var/log/pihole 挂载。

九、等价 docker run

不用 Compose 时,以下单条 docker run 与本篇 compose 配置等价(端口换用 8099/5332 以避免与本篇主实例冲突,实测已跑通):

docker run -d --name pihole-v6-run \
  -p 8099:80/tcp -p 5332:53/tcp -p 5332:53/udp \
  -e TZ="Asia/Shanghai" \
  -e FTLCONF_webserver_api_password="q1w2e3r4" \
  -e FTLCONF_dns_upstreams="223.5.5.5;119.29.29.29" \
  -v /opt/data/pihole/etc-pihole-run:/etc/pihole \
  --cap-add NET_ADMIN \
  --restart unless-stopped \
  pihole/pihole:2026.10.0

实测回显(启动后验证 DNS 与 Web 登录):

docker ps --filter name=pihole-v6-run --format '{{.Names}} {{.Status}}'
dig @127.0.0.1 -p 5332 www.baidu.com +short
curl -s -X POST http://127.0.0.1:8099/api/auth -H 'Content-Type: application/json' -d '{"password":"q1w2e3r4"}' | grep message
pihole-v6-run Up 15 seconds (health: starting)
www.a.shifen.com.
110.242.69.21
110.242.70.57
"message": "password correct"

关键解读:docker run 与 Compose 的差异只在网络——run 方式使用默认 bridge(无自定义网段与固定 IP),多实例协同场景仍建议 Compose。

十、常用运维命令

以下命令均在实测环境验证通过,是日常运维最高频的一组。

10.1 服务生命周期

cd /opt/data/pihole
docker compose ps                 # 状态
docker compose restart            # 重启(配置重读,数据不丢)
docker compose stop               # 停止
docker compose up -d              # 启动/重建
docker compose logs -f --tail 50  # 跟踪日志

10.2 进入实例执行 pihole 命令

docker exec pihole-v6-web pihole status

实测回显:

  [✓] FTL is listening on port 53
     [✓] UDP (IPv4)
     [✓] TCP (IPv4)
     [✓] UDP (IPv6)
     [✓] TCP (IPv6)

  [✓] Pi-hole blocking is enabled
docker exec pihole-v6-web pihole -g

实测回显(拦截列表重建,输出节选):

  [i] Target: https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts
  [✓] Status: Retrieval successful
  [✓] Parsed 6540 exact domains and 0 ABP-style domains (blocking, ignored 0 non-domain entries)
  [i] Number of gravity domains: 80174 (73653 unique domains)
  [✓] Done.

(上例为实测环境添加 adaway.org 列表后的重建输出:域名总数从 73634 增至 80174。)

10.3 暂停/恢复拦截

docker exec pihole-v6-web pihole disable   # 临时关闭拦截
docker exec pihole-v6-web pihole enable    # 重新开启

实测回显:

  [✓] Pi-hole disabled forever
  [✓] Pi-hole enabled forever

⚠️ 注意:enable 刚执行完的第一次查询可能仍返回真实 IP(引擎在 flush 缓存重读配置),间隔 1–2 秒再验证即恢复拦截(实测 doubleclick.net 复查返回 0.0.0.0)。

十一、常见问题

以下问题均为本篇实测过程中真实踩到并解决的案例。

Q1:宿主 53 端口被占用,DNS 起不来怎么办?

Ubuntu 22.04 的 systemd-resolved 默认监听 127.0.0.53:53(存根解析)。确认命令与实测输出:

ss -ltn | grep ':53 '
LISTEN 0      4096   127.0.0.53%lo:53         0.0.0.0:*

解决:不动系统服务,Pi-hole 的 DNS 直接映射到其他宿主端口(本篇用 5330),客户端 DNS 指向 服务器IP:5330(不支持端口的旧设备,可把路由器 DNAT 53 → 5330)。若坚持用 53 端口,需编辑 /etc/systemd/resolved.conf 设 DNSStubListener=no 后 systemctl restart systemd-resolved。

Q2:报错 Bind for 0.0.0.0:5331 failed: port is already allocated?

实测案例:尝试用 5331 端口起第二个实例时失败。定位:docker ps --format '{{.Names}}\t{{.Ports}}' 查到端口已被其他栈的实例占用。解决:换用空闲端口(ss -ltn 复核),Compose 与防火墙同步修改。宿主端口冲突在 docker compose up 阶段即报错,不会半启动。

Q3:调用 API 添加拦截报 Invalid request: Specify list to modify more precisely?

v6 API 路径顺序是 /api/domains/{deny|allow}/{exact|regex}。实测错误写法 POST /api/domains/exact/deny 返回 400,正确写法 POST /api/domains/deny/exact 后返回创建成功的 domains 记录(见 4.6 节回显)。adlist 管理同理:v6 端点是 /api/lists(GET 查看、POST /api/lists?type=block 添加,type 是 URL 查询参数而非 body 字段——实测 body 传 type 报 Specify type parameter)。

Q4:pihole CLI 添加域名报 Found invalid characters in domain comment!?

实测案例:docker exec pihole-v6-web pihole deny tracker-cli.test --comment "CLI实测" 报错。原因:CLI 对 comment 做字符集校验,中文注释被拒。解决:改用英文注释后成功:

  [✓] Added 1 domain(s):
    - tracker-cli.test

需要中文备注时走 Web 台或 REST API(API 实测接受中文 comment)。

Q5:Teleporter 导出的备份文件是什么格式?

是 zip 不是 tar.gz。实测 curl -o teleporter.zip http://127.0.0.1:8091/api/teleporter(带 SID 头)下载 24222 字节,file 识别为 Zip archive data;用 tar -tzf 解包报 This does not look like a tar archive,改 unzip -l 正常列出 5 个文件(pihole.toml、hosts、dhcp.leases、gravity.db、pihole-FTL.db)。

Q6:上游 DNS 全部不可用时什么表现?

本地记录与拦截仍正常(mydev.lan 照常解析、拦截域名照常 0.0.0.0),但新域名解析超时失败,查询日志只出现 forwarded <域名> to 223.5.5.5 而无对应 reply 记录。解决:更换上游为多运营商组合(如 223.5.5.5 + 119.29.29.29),并在 Web 台观察「上游健康」;必要时启用加密上游(DoH/DoT)绕过运营商劫持。

Q7:升级怎么做?

改 compose 中 image 为新版本 tag,然后:

cd /opt/data/pihole && docker compose pull && docker compose up -d

数据在 ./etc-pihole 挂载目录中自然延续。回滚同理改回旧 tag 重建即可。

十二、参考链接

  1. 官方打包运行时项目(docker-pi-hole):https://github.com/pi-hole/docker-pi-hole
  2. 官方 README 的 Compose 示例与能力说明:https://github.com/pi-hole/docker-pi-hole#readme
  3. Pi-hole Docker 文档(能力与配置):https://docs.pi-hole.net/docker/configuration/
  4. FTL 环境变量与配置参考:https://docs.pi-hole.net/ftldns/configfile/
  5. v6 REST API 文档:https://docs.pi-hole.net/api/
  6. 镜像 tags 列表(版本核对):https://hub.docker.com/r/pihole/pihole/tags
  7. 本平台相关篇:《Pi-hole v6.5 介绍与选型指南》