Pi-hole Docker Compose 部署指南
Pi-hole v6.5(镜像 2026.10.0)Docker Compose 从零部署指南:端口规划、compose 配置逐项说明、Web 登录/DNS 解析/拦截/Local DNS 全流程实测验证与常见问题排障
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 | 推送日期 | 说明 |
|---|---|---|
latest | 2026-10-17 | 当前稳定版滚动 tag,与 2026.10.0 相同 |
2026.10.0 | 2026-10-17 | 本次实测使用的固定版本 tag(对应核心 v6.5) |
2026.09.0 | 2026-09-19 | 上一版 |
nightly | 2026-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_password | v6 环境变量注入 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 项 |
|---|---|---|---|
TZ | Asia/Shanghai | 系统时区(日志与统计时间轴) | 系统变量 |
FTLCONF_webserver_api_password | q1w2e3r4 | Web 管理台密码(不设则随机生成) | webserver.api.password |
FTLCONF_dns_upstreams | 223.5.5.5;119.29.29.29 | 上游 DNS 列表,分号分隔 | dns.upstreams |
FTLCONF_dns_listeningMode | ALL | DNS 监听模式;官方建议默认 bridge 网络下设 ALL(实测未设时从宿主映射口查询也正常,因映射后源地址仍属私有网段) | dns.listeningMode |
FTLCONF_misc_etc_dnsmasq_d | true | 启用 /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.timeout | Web 台端口/密码/会话时长(实测登录会话 validity=1800 秒) |
[blocking] | mode(默认 NULL)、edns0ECS | 拦截模式,NULL 即拦截返回 0.0.0.0 |
[misc] | nice、privacylevel、check.load | 进程优先级、隐私级别、负载自检 |
配置修改三种途径(效果等价,都写回 toml):
- Web 管理台 Settings 页(可视化);
- REST API PATCH(自动化);
- 直接编辑宿主持久化目录中的
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 重建即可。
十二、参考链接
- 官方打包运行时项目(docker-pi-hole):https://github.com/pi-hole/docker-pi-hole
- 官方 README 的 Compose 示例与能力说明:https://github.com/pi-hole/docker-pi-hole#readme
- Pi-hole Docker 文档(能力与配置):https://docs.pi-hole.net/docker/configuration/
- FTL 环境变量与配置参考:https://docs.pi-hole.net/ftldns/configfile/
- v6 REST API 文档:https://docs.pi-hole.net/api/
- 镜像 tags 列表(版本核对):https://hub.docker.com/r/pihole/pihole/tags
- 本平台相关篇:《Pi-hole v6.5 介绍与选型指南》