BunkerWeb Docker Compose 部署指南
BunkerWeb 1.6.16 Docker Compose 从零部署实录:官方四服务编排(WAF 数据面 + scheduler + UI + PostgreSQL)、显式子网三网络规划、UI 管理员预置登录、反代后端静态站点与 WAF 拦截闭环验证,含等价 docker run 与真实排障
BunkerWeb Docker Compose 部署指南
目标:在 Ubuntu 22.04 服务器上用 Docker Compose 从零部署 BunkerWeb 1.6.16 全套组件(WAF 数据面 + scheduler 调度器 + Web UI + PostgreSQL 数据库),并完成管理界面登录、后端站点反代接入与 WAF 拦截闭环验证。全部命令与回显均在 Ubuntu 22.04.4 LTS 实测通过;组件定位与选型背景见《BunkerWeb 1.6.16 介绍与选型指南》,逐项配置调优见《BunkerWeb 1.6.16 配置参数详解》,防护加固实战见《BunkerWeb 安全加固指南》。
一、环境信息
- 操作系统:Ubuntu 22.04.4 LTS(x86_64)
- Docker:29.8.1(Docker Compose v5.5.1,compose v2 插件语法)
- BunkerWeb 版本:1.6.16(官方最新稳定版,GitHub Releases 2026-10-14 发布;编排参考官方仓库
misc/integrations/docker.postgres.ui.yml) - 内核引擎:bunkerweb 容器内置
nginx/1.30.5(实测nginx -v回显) - 宿主端口规划(实测复核后):
| 用途 | 宿主端口 | 容器端口 | 说明 |
|---|---|---|---|
| HTTP 入口 | 8117 | 8080 | BunkerWeb 对外 HTTP |
| HTTPS 入口 | 9443 | 8443 | 预分配 8443 时 ss -ltn 复核已被其他服务占用(docker-proxy 监听),调整为 9443 |
| Web UI | 7000 | 7000 | 管理界面直连端口(官方亦支持仅经反代访问 UI) |
⚠️ 注意:预分配端口动工前必须
ss -ltn复核。本次实测时宿主 8443 已被其他服务占用,故 HTTPS 宿主端口调整为 9443,后文所有命令与回显均按此执行。
- 网络规划(显式子网,避开 192.168.0.0/16 与 100.64.0.0/16;10.89.46.0/24 已被同机其他项目占用故顺延):
| 网络名 | 子网 | 用途 |
|---|---|---|
| bw-universe | 10.89.45.0/24 | 数据面与 scheduler/UI 之间的内部 API 通信 |
| bw-services | 10.89.47.0/24 | 客户端流量面,后端站点与数据面互通 |
| bw-db | 10.89.48.0/24 | 数据库私网,仅 scheduler/UI/DB 互通 |
- 项目目录:
/opt/data/bunkerweb-1-6-16/(compose 文件与示例后端站点)
二、镜像选型说明
官方在 Docker Hub 发布按组件拆分的镜像,全部标签固定为版本号(以 1.6.16 为例):
| 镜像 | 用途 | 说明 |
|---|---|---|
bunkerity/bunkerweb:1.6.16 | WAF 数据面 | 封装 NGINX,承接客户端流量,执行防护规则 |
bunkerity/bunkerweb-scheduler:1.6.16 | 调度器 | 从数据库读取配置、渲染 NGINX 配置并经内部 API 下发,周期执行证书/封禁等作业 |
bunkerity/bunkerweb-ui:1.6.16 | 管理界面 | Flask + gunicorn 实现的 Web 管理端(服务管理/配置/日志/插件) |
postgres:18-alpine | 配置数据库 | 官方编排默认演示后端之一;UI 与 scheduler 必须共享同一数据库才能联动 |
选型解读:① 数据面/调度器/管理面三者镜像独立发布,升级可单独进行,但三镜像版本必须一致(官方要求 scheduler 与数据面版本匹配);② 数据库选择上,官方 misc/integrations 目录提供 SQLite/MySQL/MariaDB/PostgreSQL/Oracle 全套编排文件——SQLite 仅适合单机测试(UI 与 scheduler 分属不同进程组,共享本地文件有并发写限制),生产或需要 UI 联动管理时选独立数据库服务(本篇采用 PostgreSQL,与官方 docker.postgres.ui.yml 一致);③ 另有 bunkerweb-all-in-one:1.6.16 集成镜像(单容器内置全部组件 + UI),适合快速体验,不适合作为标准部署形态。
拉取镜像(Docker 已配置代理时国外镜像自动加速):
docker pull bunkerity/bunkerweb:1.6.16
docker pull bunkerity/bunkerweb-scheduler:1.6.16
docker pull bunkerity/bunkerweb-ui:1.6.16
docker pull postgres:18-alpine
实测回显(节选首尾):
1.6.16: Pulling from bunkerity/bunkerweb
...
Status: Downloaded newer image for bunkerity/bunkerweb:1.6.16
docker.io/bunkerity/bunkerweb:1.6.16
...
Status: Image is up to date for postgres:18-alpine
docker.io/library/postgres:18-alpine
三、前置条件
- 服务器已安装 Docker 与 compose 插件(本机实测
Docker version 29.8.1、Docker Compose version v5.5.1); - 端口未被占用:部署前
ss -ltn复核 HTTP/HTTPS/UI 三个宿主端口。实测记录:
ss -ltn | grep -E ':(8117|8443)\b'
实测回显(8443 已被占用,8117 空闲):
LISTEN 0 4096 0.0.0.0:8443 0.0.0.0:*
LISTEN 0 4096 [::]:8443 [::]:*
sudo ss -ltnp | grep ':8443'
实测回显(占用方为 docker-proxy,即其他已部署服务映射):
LISTEN 0 4096 0.0.0.0:8443 0.0.0.0:*
users:(("docker-proxy",pid=946828,fd=8))
LISTEN 0 4096 0.0.0.0:8443 0.0.0.0:*
users:(("docker-proxy",pid=946835,fd=8))
- 规划三个显式子网网络(下一步创建),并确认主机默认路由不受影响;
- 一个用于测试的后端站点:本文用官方
nginx:alpine承载一个静态页作为被保护对象(生产中换成任意 HTTP 服务即可); - 磁盘与内存:官方要求测试环境 2 vCPU/8 GB 起步(Quickstart guide),全套镜像约 2.5 GB。
四、部署步骤
4.1 创建显式子网网络
官方编排将数据面/流量面/数据库分属三个网络,每个网络显式指定子网(禁用 192.168.0.0/16 与 100.64.0.0/16 网段,10.89.46.0/24 已被本机其他项目占用故顺延取 47/48):
docker network create -d bridge --subnet 10.89.45.0/24 bw-universe
docker network create -d bridge --subnet 10.89.47.0/24 bw-services
docker network create -d bridge --subnet 10.89.48.0/24 bw-db
三条命令实测回显(每条输出一个网络 ID):
daf066bce4771684efd280ad64567ddd5e8535534aaac37beff4c9e14a3289ea
674377e87b2a0ccfe428cf29dd06cb001dd91209e1718fb49a4662428fbb5372
d989ee1a738ce791312d4c604b7216183dc6527926101ac52e3715e35c3d4696
创建后核对子网与宿主路由(默认路由必须仍走物理网卡):
docker network inspect bw-universe bw-services bw-db --format '{{.Name}} {{range .IPAM.Config}}{{.Subnet}}{{end}}'
ip route get 192.168.1.62
实测回显:
bw-universe 10.89.45.0/24
bw-services 10.89.47.0/24
bw-db 10.89.48.0/24
192.168.1.62 via 192.168.146.2 dev ens33 src 192.168.146.128 uid 0
cache
三个子网均不落在禁用段,且到局域网网关的探测仍走 ens33,网段安全。
4.2 建立项目目录与后端站点页面
mkdir -p /opt/data/bunkerweb-1-6-16/myapp
写入一个最简静态首页 myapp/index.html(作为被保护站点的响应体):
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>MyApp - BunkerWeb Protected Site</title>
</head>
<body>
<h1>Hello from MyApp</h1>
<p>This static page is protected by BunkerWeb.</p>
</body>
</html>
4.3 编写 docker-compose.yml
以下编排依据官方 v1.6.16 仓库 misc/integrations/docker.postgres.ui.yml 改造:网络改为引用 4.1 创建的外部网络、端口映射按本机复核结果调整、站点名用 app.local、管理员账号预置。保存为 /opt/data/bunkerweb-1-6-16/docker-compose.yml:
x-env: &env
DATABASE_URI: "postgresql://bunkerweb:q1w2e3r4@bw-db:5432/db"
services:
bunkerweb:
image: bunkerity/bunkerweb:1.6.16
container_name: bunkerweb-1-6-16-waf
ports:
- "8117:8080"
- "9443:8443"
labels:
- "bunkerweb.INSTANCE=yes"
environment:
API_WHITELIST_IP: "127.0.0.0/8 10.89.45.0/24"
restart: "unless-stopped"
networks:
- bw-universe
- bw-services
bw-scheduler:
image: bunkerity/bunkerweb-scheduler:1.6.16
container_name: bunkerweb-1-6-16-scheduler
depends_on:
- bunkerweb
- bw-db
volumes:
- bw-storage:/data
environment:
<<: *env
BUNKERWEB_INSTANCES: "bunkerweb"
SERVER_NAME: "app.local"
API_WHITELIST_IP: "127.0.0.0/8 10.89.45.0/24"
MULTISITE: "yes"
SERVE_FILES: "no"
DISABLE_DEFAULT_SERVER: "yes"
USE_CLIENT_CACHE: "yes"
USE_GZIP: "yes"
app.local_USE_REVERSE_PROXY: "yes"
app.local_REVERSE_PROXY_URL: "/"
app.local_REVERSE_PROXY_HOST: "http://myapp:80"
restart: "unless-stopped"
networks:
- bw-universe
- bw-db
bw-ui:
image: bunkerity/bunkerweb-ui:1.6.16
container_name: bunkerweb-1-6-16-ui
depends_on:
- bw-db
ports:
- "7000:7000"
environment:
<<: *env
ADMIN_USERNAME: "admin"
# q1w2e3r4 的 bcrypt 哈希(ADMIN_PASSWORD 接受哈希形式并跳过明文强度校验)
ADMIN_PASSWORD: "$$2b$$12$$VKTSBT6GxD7aVFUfU2jRr.kXu8AsBIyoeD1gyUP8/PQC6Qrv10k/a"
OVERRIDE_ADMIN_CREDS: "yes"
volumes:
- bw-ui-data:/data
restart: "unless-stopped"
networks:
- bw-universe
- bw-db
bw-db:
image: postgres:18-alpine
container_name: bunkerweb-1-6-16-db
environment:
POSTGRES_USER: "bunkerweb"
POSTGRES_PASSWORD: "q1w2e3r4"
POSTGRES_DB: "db"
volumes:
- bw-data:/var/lib/postgresql
restart: "unless-stopped"
networks:
- bw-db
myapp:
image: nginx:alpine
container_name: bunkerweb-1-6-16-myapp
volumes:
- ./myapp/index.html:/usr/share/nginx/html/index.html:ro
restart: "unless-stopped"
networks:
- bw-services
volumes:
bw-data:
bw-storage:
bw-ui-data:
networks:
bw-universe:
external: true
bw-services:
external: true
bw-db:
external: true
逐项说明:
x-env: &env+<<: *env:YAML 锚点,把DATABASE_URI注入 scheduler 与 UI,保证两者连到同一 PostgreSQL(联动关键);API_WHITELIST_IP:官方要求该设置在数据面与 scheduler 上镜像一致,此处放行本机回环与 bw-universe 网段,限制内部 API 只能被调度器调用;BUNKERWEB_INSTANCES: "bunkerweb":官方要求必须指定数据面实例名(与 service 名一致);SERVER_NAME: "app.local":受管站点域名,测试环境用自定义域名(访问时以Host: app.local头匹配),生产换成真实域名;MULTISITE: "yes":启用按站点独立配置(每站点可单独开关防护项),站点级设置以域名_设置名形式书写;SERVE_FILES: "no"+DISABLE_DEFAULT_SERVER: "yes":本方案所有流量都走反代,关闭默认站点与本地静态文件服务,避免未匹配域名的请求落到默认页;app.local_REVERSE_PROXY_*:把app.local的请求反代到后端http://myapp:80(myapp 与数据面同在 bw-services 网络,容器名即 DNS);ADMIN_USERNAME/ADMIN_PASSWORD/OVERRIDE_ADMIN_CREDS:预置 UI 管理员。注意官方密码策略强制要求 8 位以上且含大小写字母/数字/特殊字符,q1w2e3r4不满足明文校验,因此用其 bcrypt 哈希注入(官方文档明确:ADMIN_PASSWORD接受$2a$/$2b$/$2y$哈希并按原样存储、跳过强度校验),登录时仍使用明文q1w2e3r4;生成哈希:
python3 -c "import bcrypt; print(bcrypt.hashpw(b'q1w2e3r4', bcrypt.gensalt(rounds=12)).decode())"
实测回显:
$2b$12$VKTSBT6GxD7aVFUfU2jRr.kXu8AsBIyoeD1gyUP8/PQC6Qrv10k/a
⚠️ 注意:compose 文件中
$是变量引用前缀,哈希里的$2b$12$...必须写成$$2b$$12$$...($$转义),否则 compose 解析会把哈希改写导致登录失败。OVERRIDE_ADMIN_CREDS: "yes":首次启动失败后重建账号时需要;如密码策略冲突已踩坑(见"十一、常见问题"),重建 bw-ui 容器时务必保留该项。
4.4 启动整套服务
cd /opt/data/bunkerweb-1-6-16
docker compose up -d
实测回显:
Container bunkerweb-1-6-16-db Starting
Container bunkerweb-1-6-16-myapp Started
Container bunkerweb-1-6-16-waf Started
Container bunkerweb-1-6-16-db Started
Container bunkerweb-1-6-16-scheduler Starting
Container bunkerweb-1-6-16-ui Starting
Container bunkerweb-1-6-16-scheduler Started
Container bunkerweb-1-6-16-ui Started
4.5 验证容器健康状态
等待约 60~90 秒(scheduler 首次生成配置并下发),逐个确认:
docker ps --filter name=bunkerweb --format '{{.Names}}\t{{.Status}}'
实测回显:
bunkerweb-1-6-16-ui Up About a minute (healthy)
bunkerweb-1-6-16-scheduler Up 4 minutes (healthy)
bunkerweb-1-6-16-waf Up 4 minutes (healthy)
bunkerweb-1-6-16-db Up 4 minutes
bunkerweb-1-6-16-myapp Up 4 minutes
数据面(waf)、调度器(scheduler)、管理界面(ui)三个带健康检查的服务全部 healthy;db 与 myapp 无内置 healthcheck(镜像未定义),以 Up 与连通性验证为准。版本核实:
docker exec bunkerweb-1-6-16-waf nginx -v
docker exec bunkerweb-1-6-16-waf cat /usr/share/bunkerweb/VERSION
实测回显:
nginx version: nginx/1.30.5
1.6.16
调度器下发配置成功的关键日志(docker logs bunkerweb-1-6-16-scheduler 节选):
[2026-10-19 05:51:45 +0000] [GENERATOR] [111] [ℹ️ ] - Generator successfully executed !
[2026-10-19 05:51:45 +0000] [SCHEDULER] [36] [ℹ️ ] - Sending /etc/nginx to all reachable BunkerWeb instances ...
[2026-10-19 05:51:46 +0000] [SCHEDULER] [36] [ℹ️ ] - Successfully sent /etc/nginx folder to reachable BunkerWeb instances
4.6 验证 UI 登录
浏览器访问 http://<服务器IP>:7000,用 admin / q1w2e3r4 登录。命令行等价验证(两步:取登录页 CSRF 令牌 → 提交表单 → 跟随会话访问主页):
curl -s -c /tmp/bw_c.txt http://127.0.0.1:7000/login | grep -oP 'name="csrf_token" value="\K[^"]+' | head -c 40; echo
实测回显(CSRF 令牌前 40 字符):
ImQ4MGRlMWYyNTFiZmQ2YzRiYWE0YT...
CSRF=$(curl -s -c /tmp/bw_c.txt http://127.0.0.1:7000/login | grep -oP 'name="csrf_token" value="\K[^"]+')
curl -s -b /tmp/bw_c.txt -c /tmp/bw_c.txt -o /dev/null -w 'POST /login -> %{http_code} Location: %{redirect_url}\n' -X POST --data-urlencode "csrf_token=$CSRF" --data-urlencode "username=admin" --data-urlencode "password=q1w2e3r4" http://127.0.0.1:7000/login
curl -s -b /tmp/bw_c.txt -o /dev/null -w 'GET /home -> %{http_code}\n' http://127.0.0.1:7000/home
实测回显:
POST /login -> 302 Location: http://127.0.0.1:7000/loading?next=/home
GET /home -> 200
登录后 302 跳转 loading 页、/home 返回 200 即认证通过;页面正文含当前用户 admin 与 BunkerWeb 全局概览内容。
4.7 验证后端站点反代接入
BunkerWeb 已监听宿主 8117(HTTP)。用 Host: app.local 头访问:
curl -s -o /dev/null -w 'HTTP %{http_code} in %{time_total}s\n' -H 'Host: app.local' http://127.0.0.1:8117/
curl -s -H 'Host: app.local' http://127.0.0.1:8117/ | head -5
实测回显:
HTTP 200 in 0.010374s
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>MyApp - BunkerWeb Protected Site</title>
请求经 8117 进入 BunkerWeb → 命中 app.local 站点 → 反代到 myapp → 返回静态页 200,反代链路打通。宿主端口监听确认:
ss -ltn | grep -E ':(8117|9443|7000)\b'
实测回显:
LISTEN 0 4096 0.0.0.0:7000 0.0.0.0:*
LISTEN 0 4096 0.0.0.0:8117 0.0.0.0:*
LISTEN 0 4096 0.0.0.0:9443 0.0.0.0:*
LISTEN 0 4096 [::]:7000 [::]:*
LISTEN 0 4096 [::]:8117 [::]:*
LISTEN 0 4096 [::]:9443 [::]:*
4.8 验证 WAF 拦截闭环
对同一路径分别发起正常请求与典型攻击特征请求做对照(详细攻防对照与参数调优见《BunkerWeb 安全加固指南》):
curl -s -o /dev/null -w 'HTTP %{http_code}\n' -H 'Host: app.local' 'http://127.0.0.1:8117/'
curl -s -o /dev/null -w 'HTTP %{http_code}\n' -H 'Host: app.local' 'http://127.0.0.1:8117/?id=1%20UNION%20SELECT%201,2,3'
curl -s -o /dev/null -w 'HTTP %{http_code}\n' -H 'Host: app.local' 'http://127.0.0.1:8117/?file=../../etc/passwd'
实测回显:
HTTP 200
HTTP 403
HTTP 429
- 正常请求 200:放行;
- SQL 注入特征(
UNION SELECT)403:ModSecurity CRS 规则拦截,响应页标题为403 | Forbidden(默认拦截页); - 路径遍历(
../../etc/passwd)429:前序异常请求已触发行为计分,命中速率/行为防护(连续异常请求快速升级为限速拒绝),说明 bad behavior/限速链路同样在工作。
拦截页关键内容(403 响应体实测节选):
<title>403 | Forbidden</title>
<h1 class="error-code">403</h1>
至此"部署 → UI 登录 → 反代接入 → 拦截生效"四个验收点全部闭环。
五、数据持久化说明
compose 定义三个命名卷,对应三份数据:
| 卷 | 挂载点 | 内容 | 备份建议 |
|---|---|---|---|
| bw-data | /var/lib/postgresql(bw-db) | 配置库全部数据(站点配置、设置、UI 用户) | 核心数据,定期 pg_dump 或卷快照 |
| bw-storage | /data(bw-scheduler) | 调度器缓存、Let's Encrypt 账户与证书、作业数据 | 随配置一起备份 |
| bw-ui-data | /data(bw-ui) | UI 会话密钥(Flask secret、TOTOT 加密键、Biscuit 键) | 丢失则全部会话失效,需重新登录/重新绑定 2FA |
验证卷已创建并落盘:
docker volume ls --format '{{.Name}}' | grep bunkerweb
实测回显:
bunkerweb-1-6-16_bw-data
bunkerweb-1-6-16_bw-storage
bunkerweb-1-6-16_bw-ui-data
(卷名前缀为 compose 项目目录名,实际以 docker volume ls 输出为准。)升级或重建容器不影响这三份数据;若要彻底重置(含 UI 管理员账号),删除三个卷后重新 up -d。
六、环境变量表
核心环境变量与实测取值(完整列表见官方文档 Settings 章节):
| 变量 | 作用域 | 实测取值 | 说明 |
|---|---|---|---|
| DATABASE_URI | scheduler、UI | postgresql://bunkerweb:q1w2e3r4@bw-db:5432/db | 配置库连接串;UI 与 scheduler 必须一致 |
| API_WHITELIST_IP | bunkerweb、bw-scheduler | 127.0.0.0/8 10.89.45.0/24 | 内部 API 白名单(两侧必须镜像一致) |
| BUNKERWEB_INSTANCES | bw-scheduler | bunkerweb | 数据面实例名(service 名) |
| SERVER_NAME | bw-scheduler | app.local | 受管站点域名列表(空格分隔多个) |
| MULTISITE | bw-scheduler | yes | 按站点独立配置开关 |
| SERVE_FILES | bw-scheduler | no | 是否由数据面直接服务本地静态文件 |
| DISABLE_DEFAULT_SERVER | bw-scheduler | yes | 关闭默认站点(未匹配域名不响应) |
| USE_CLIENT_CACHE | bw-scheduler | yes | 客户端缓存控制 |
| USE_GZIP | bw-scheduler | yes | Gzip 压缩 |
<域名>_USE_REVERSE_PROXY | bw-scheduler | yes | 站点级:启用反代 |
<域名>_REVERSE_PROXY_URL | bw-scheduler | / | 站点级:反代路径前缀 |
<域名>_REVERSE_PROXY_HOST | bw-scheduler | http://myapp:80 | 站点级:后端地址 |
| ADMIN_USERNAME | bw-ui | admin | UI 管理员账号 |
| ADMIN_PASSWORD | bw-ui | bcrypt 哈希 | 支持明文(需满足强度策略)或 $2a$/$2b$/$2y$ 哈希(跳过策略) |
| OVERRIDE_ADMIN_CREDS | bw-ui | yes | 即使已有管理员账号也强制重播种 |
七、配置参数说明
部署即生效的最小参数集已含三组关键配置,理解它们便于后续扩容:
- API 安全组(
API_WHITELIST_IP,配合默认API_HTTP_PORT=5000/API_SERVER_NAME=bwapi):内部 API 是配置下发唯一通道,白名单必须只放行 bw-universe 网段;如需再加固可设置API_TOKEN(scheduler 与数据面同步设置,下发请求将携带Authorization: Bearer <token>); - 站点组(
SERVER_NAME/MULTISITE/DISABLE_DEFAULT_SERVER):新增受管站点 =SERVER_NAME追加域名 + 追加一组域名_前缀设置 +docker compose up -d重建 scheduler;域名在 UI 中也可视化管理(见《BunkerWeb 1.6.16 配置参数详解》); - UI 账号组(
ADMIN_USERNAME/ADMIN_PASSWORD/OVERRIDE_ADMIN_CREDS):官方策略要求密码含大小写/数字/特殊字符且至少 8 位;2FA(TOTP)可在 UI 个人设置中绑定,加密键自动生成于 bw-ui-data 卷。
八、配置文件挂载
本方案的持久化以数据库为中心:站点与全局配置存 PostgreSQL(bw-data 卷),不在宿主机维护 nginx 配置文件——数据面 NGINX 配置由 scheduler 动态渲染后经内部 API 下发(这是 BunkerWeb 与手工维护 Nginx 配置的本质区别)。需要宿主机挂载的场景只有两类:
- UI 日志落盘(可选):给 bw-ui 增加卷映射
- ./logs:/var/log/bunkerweb并配合LOG_TYPES=file、LOG_FILE_PATH=/var/log/bunkerweb/ui.log; - 后端站点内容:如本方案
./myapp/index.html只读挂载进 myapp;生产中后端通常是独立应用服务器,不需要挂载。
验证配置确实由数据库驱动:改动站点设置应在 UI 或数据库生效后由 scheduler 重新下发(观察 docker logs -f bunkerweb-1-6-16-scheduler 中的 Generator successfully executed ! 与 Successfully sent /etc/nginx)。
九、等价 docker run 形态
compose 的等价手工启动形态(便于理解各服务间连接关系;三网络已按 4.1 创建)。逐个执行,每条命令独立说明:
启动配置数据库(bw-db 网络,账号/库名与 compose 一致):
docker run -d --name bunkerweb-1-6-16-db \
--network bw-db \
-e POSTGRES_USER=bunkerweb \
-e POSTGRES_PASSWORD=q1w2e3r4 \
-e POSTGRES_DB=db \
-v bw-data:/var/lib/postgresql \
--restart unless-stopped \
postgres:18-alpine
启动被保护后端站点(bw-services 网络):
docker run -d --name bunkerweb-1-6-16-myapp \
--network bw-services \
-v /opt/data/bunkerweb-1-6-16/myapp/index.html:/usr/share/nginx/html/index.html:ro \
--restart unless-stopped \
nginx:alpine
启动数据面(同时接入 universe 与 services 两个网络,映射 HTTP/HTTPS 端口):
docker run -d --name bunkerweb-1-6-16-waf \
--network bw-universe \
--network-alias bunkerweb \
-p 8117:8080 \
-p 9443:8443 \
-l bunkerweb.INSTANCE=yes \
-e API_WHITELIST_IP="127.0.0.0/8 10.89.45.0/24" \
--restart unless-stopped \
bunkerity/bunkerweb:1.6.16
docker network connect bw-services bunkerweb-1-6-16-waf
启动调度器(连 universe 与 db 网络,注入全部站点设置):
docker run -d --name bunkerweb-1-6-16-scheduler \
--network bw-universe \
-e DATABASE_URI="postgresql://bunkerweb:q1w2e3r4@bw-db:5432/db" \
-e BUNKERWEB_INSTANCES=bunkerweb \
-e SERVER_NAME=app.local \
-e API_WHITELIST_IP="127.0.0.0/8 10.89.45.0/24" \
-e MULTISITE=yes \
-e SERVE_FILES=no \
-e DISABLE_DEFAULT_SERVER=yes \
-e USE_CLIENT_CACHE=yes \
-e USE_GZIP=yes \
-e app.local_USE_REVERSE_PROXY=yes \
-e app.local_REVERSE_PROXY_URL=/ \
-e app.local_REVERSE_PROXY_HOST=http://myapp:80 \
-v bw-storage:/data \
--restart unless-stopped \
bunkerity/bunkerweb-scheduler:1.6.16
docker network connect bw-db bunkerweb-1-6-16-scheduler
启动管理界面(连 universe 与 db 网络,映射 7000):
docker run -d --name bunkerweb-1-6-16-ui \
--network bw-universe \
-p 7000:7000 \
-e DATABASE_URI="postgresql://bunkerweb:q1w2e3r4@bw-db:5432/db" \
-e ADMIN_USERNAME=admin \
-e ADMIN_PASSWORD='$2b$12$VKTSBT6GxD7aVFUfU2jRr.kXu8AsBIyoeD1gyUP8/PQC6Qrv10k/a' \
-e OVERRIDE_ADMIN_CREDS=yes \
-v bw-ui-data:/data \
--restart unless-stopped \
bunkerity/bunkerweb-ui:1.6.16
docker network connect bw-db bunkerweb-1-6-16-ui
💡 说明:docker run 形态中多网络接入用
docker run --network+docker network connect组合实现;调度器环境变量携带.的键(如app.local_...)直接以-e KEY=value传入即可。日常运维建议统一用 compose(配置即代码,免手工对账)。
十、常用运维命令
cd /opt/data/bunkerweb-1-6-16
docker compose ps # 全套服务状态(同 4.5 回显)
docker compose logs -f bunkerweb # 跟踪数据面日志
docker compose logs -f bw-scheduler # 跟踪调度器日志(配置下发/作业)
docker compose restart bw-ui # 重启单个服务
docker compose up -d # 修改 compose 后增量生效
docker compose down # 停止并移除容器(保留三个数据卷)
查看数据面实时访问日志(每次请求一行,含拦截记录):
docker logs -f bunkerweb-1-6-16-waf 2>&1 | grep -i 'app.local' &
curl -s -o /dev/null -w '%{http_code}\n' -H 'Host: app.local' http://127.0.0.1:8117/
升级版本流程:改 compose 中四个镜像 tag(三组件同版本)→ docker compose up -d → 观察 scheduler 日志完成配置迁移。回滚即改回旧 tag 重新 up(数据卷结构跨小版本兼容;跨大版本先读官方 Upgrading 文档)。
十一、常见问题
Q1:bw-ui 反复 unhealthy,日志报密码不够强?
实测现象:docker ps 显示 bunkerweb-1-6-16-ui ... (unhealthy),日志(docker logs bunkerweb-1-6-16-ui)报错原文:
[2026-10-19 05:51:27 +0000] [UI] [28] [❌] - The admin password is not strong enough. It must contain at least 8 characters, including at least 1 uppercase letter, 1 lowercase letter, 1 number and 1 special character.
[2026-10-19 05:51:27] - ENTRYPOINT - ℹ️ - Web UI stopped with exit code 1
原因:官方 UI 强制密码策略(≥8 位 + 大写 + 小写 + 数字 + 特殊字符),明文 q1w2e3r4 不满足。解决方案(本文 4.3 已采用):改用 bcrypt 哈希注入——官方文档明确 ADMIN_PASSWORD 接受 $2a$/$2b$/$2y$ 哈希并跳过强度校验:
python3 -c "import bcrypt; print(bcrypt.hashpw(b'q1w2e3r4', bcrypt.gensalt(rounds=12)).decode())"
把哈希写入 compose($ 全部转义为 $$),并加 OVERRIDE_ADMIN_CREDS: "yes" 后重建:
docker compose up -d --force-recreate bw-ui
实测结果:约 30 秒后 docker ps 显示 bunkerweb-1-6-16-ui Up 30 seconds (healthy),日志显示 gunicorn 监听:
[2026-10-19 05:54:55 +0000] [UI] [25] [ℹ️ ] - Starting gunicorn 26.2.0
[2026-10-19 05:54:57 +0000] [UI] [25] [ℹ️ ] - Listening at: http://0.0.0.0:7000 (25)
用 admin/q1w2e3r4 登录成功(4.6 回显)。
Q2:docker network create 报 Pool overlaps?
实测现象:创建第二个网络时报 Error response from daemon: invalid pool request: Pool overlaps with other one on this address space。原因:该子网已被本机其他 compose 项目占用。排查与解决:docker network inspect 列出全部网络子网,换一个未占用子网即可(本机 10.89.46.0/24 被占用,顺延取 10.89.47.0/24):
docker network inspect $(docker network ls --format '{{.Name}}') \
--format '{{.Name}} {{range .IPAM.Config}}{{.Subnet}} {{end}}' | sort
Q3:宿主 8443 端口映射失败或冲突?
同 Q2 思路先 ss -ltnp | grep :8443 确认占用方;若是其他服务占用(本机为 docker-proxy),把 BunkerWeb HTTPS 映射改为其他空闲宿主端口(本文取 9443)。容器内端口恒为 8443 不变。
Q4:UI 启动日志偶见 403 Client Error: rate limit exceeded ... api.github.com?
实测日志:
[2026-10-19 05:54:56 +0000] [UI] [25] [❌] - Exception while fetching latest release information: 403 Client Error: rate limit exceeded for url: https://api.github.com/repos/bunkerity/bunkerweb/releases?per_page=100&page=1
这是 UI 周期检查"最新版本"时 GitHub API 匿名配额耗尽所致,仅影响"检查更新"提示,不影响登录与配置管理;稍后自愈或忽略即可。
Q5:登录 /login 时 POST 返回 303 跳回 reason=session_expired?
登录表单带 CSRF 校验:必须先 GET /login 拿到 cookie 与 csrf_token 隐藏域,再携带两者 POST(4.6 流程)。直接裸 POST 会话不存在会被重定向。
Q6:访问返回 429 而非预期 403/200?
BunkerWeb 默认开启行为防护:短时间内连续异常请求会被升级为限速拒绝(429 Too Many Requests)。这在测试时容易误判"拦截规则没生效"——实际是行为链路先触发了。等待封禁窗口过期或从 UI 调整 bad behavior/限速参数(见《BunkerWeb 安全加固指南》)。
十二、参考链接
- 官方编排参考(v1.6.16 misc/integrations/docker.postgres.ui.yml):https://github.com/bunkerity/bunkerweb/blob/v1.6.16/misc/integrations/docker.postgres.ui.yml
- 官方文档 Docker 集成:https://docs.bunkerweb.io/latest/integrations/
- 官方文档 Quickstart guide:https://docs.bunkerweb.io/latest/quickstart-guide/
- 官方文档 Web UI(Setup wizard / 管理员预置 / 密码策略):https://docs.bunkerweb.io/latest/web-ui/
- GitHub Releases(v1.6.16):https://github.com/bunkerity/bunkerweb/releases/tag/v1.6.16
- 系列文档:《BunkerWeb 1.6.16 介绍与选型指南》《BunkerWeb 1.6.16 配置参数详解》《BunkerWeb 安全加固指南》