BunkerWeb Docker Compose 部署指南

BunkerWeb 1.6.16 Docker Compose 从零部署实录:官方四服务编排(WAF 数据面 + scheduler + UI + PostgreSQL)、显式子网三网络规划、UI 管理员预置登录、反代后端静态站点与 WAF 拦截闭环验证,含等价 docker run 与真实排障

BunkerWeb2026/10/19

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 入口81178080BunkerWeb 对外 HTTP
HTTPS 入口94438443预分配 8443 时 ss -ltn 复核已被其他服务占用(docker-proxy 监听),调整为 9443
Web UI70007000管理界面直连端口(官方亦支持仅经反代访问 UI)

⚠️ 注意:预分配端口动工前必须 ss -ltn 复核。本次实测时宿主 8443 已被其他服务占用,故 HTTPS 宿主端口调整为 9443,后文所有命令与回显均按此执行。

  • 网络规划(显式子网,避开 192.168.0.0/16 与 100.64.0.0/16;10.89.46.0/24 已被同机其他项目占用故顺延):
网络名子网用途
bw-universe10.89.45.0/24数据面与 scheduler/UI 之间的内部 API 通信
bw-services10.89.47.0/24客户端流量面,后端站点与数据面互通
bw-db10.89.48.0/24数据库私网,仅 scheduler/UI/DB 互通
  • 项目目录:/opt/data/bunkerweb-1-6-16/(compose 文件与示例后端站点)

二、镜像选型说明

官方在 Docker Hub 发布按组件拆分的镜像,全部标签固定为版本号(以 1.6.16 为例):

镜像用途说明
bunkerity/bunkerweb:1.6.16WAF 数据面封装 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

三、前置条件

  1. 服务器已安装 Docker 与 compose 插件(本机实测 Docker version 29.8.1、Docker Compose version v5.5.1);
  2. 端口未被占用:部署前 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))
  1. 规划三个显式子网网络(下一步创建),并确认主机默认路由不受影响;
  2. 一个用于测试的后端站点:本文用官方 nginx:alpine 承载一个静态页作为被保护对象(生产中换成任意 HTTP 服务即可);
  3. 磁盘与内存:官方要求测试环境 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_URIscheduler、UIpostgresql://bunkerweb:q1w2e3r4@bw-db:5432/db配置库连接串;UI 与 scheduler 必须一致
API_WHITELIST_IPbunkerweb、bw-scheduler127.0.0.0/8 10.89.45.0/24内部 API 白名单(两侧必须镜像一致)
BUNKERWEB_INSTANCESbw-schedulerbunkerweb数据面实例名(service 名)
SERVER_NAMEbw-schedulerapp.local受管站点域名列表(空格分隔多个)
MULTISITEbw-scheduleryes按站点独立配置开关
SERVE_FILESbw-schedulerno是否由数据面直接服务本地静态文件
DISABLE_DEFAULT_SERVERbw-scheduleryes关闭默认站点(未匹配域名不响应)
USE_CLIENT_CACHEbw-scheduleryes客户端缓存控制
USE_GZIPbw-scheduleryesGzip 压缩
<域名>_USE_REVERSE_PROXYbw-scheduleryes站点级:启用反代
<域名>_REVERSE_PROXY_URLbw-scheduler/站点级:反代路径前缀
<域名>_REVERSE_PROXY_HOSTbw-schedulerhttp://myapp:80站点级:后端地址
ADMIN_USERNAMEbw-uiadminUI 管理员账号
ADMIN_PASSWORDbw-uibcrypt 哈希支持明文(需满足强度策略)或 $2a$/$2b$/$2y$ 哈希(跳过策略)
OVERRIDE_ADMIN_CREDSbw-uiyes即使已有管理员账号也强制重播种

七、配置参数说明

部署即生效的最小参数集已含三组关键配置,理解它们便于后续扩容:

  1. API 安全组(API_WHITELIST_IP,配合默认 API_HTTP_PORT=5000/API_SERVER_NAME=bwapi):内部 API 是配置下发唯一通道,白名单必须只放行 bw-universe 网段;如需再加固可设置 API_TOKEN(scheduler 与数据面同步设置,下发请求将携带 Authorization: Bearer <token>);
  2. 站点组(SERVER_NAME/MULTISITE/DISABLE_DEFAULT_SERVER):新增受管站点 = SERVER_NAME 追加域名 + 追加一组 域名_ 前缀设置 + docker compose up -d 重建 scheduler;域名在 UI 中也可视化管理(见《BunkerWeb 1.6.16 配置参数详解》);
  3. UI 账号组(ADMIN_USERNAME/ADMIN_PASSWORD/OVERRIDE_ADMIN_CREDS):官方策略要求密码含大小写/数字/特殊字符且至少 8 位;2FA(TOTP)可在 UI 个人设置中绑定,加密键自动生成于 bw-ui-data 卷。

八、配置文件挂载

本方案的持久化以数据库为中心:站点与全局配置存 PostgreSQL(bw-data 卷),不在宿主机维护 nginx 配置文件——数据面 NGINX 配置由 scheduler 动态渲染后经内部 API 下发(这是 BunkerWeb 与手工维护 Nginx 配置的本质区别)。需要宿主机挂载的场景只有两类:

  1. UI 日志落盘(可选):给 bw-ui 增加卷映射 - ./logs:/var/log/bunkerweb 并配合 LOG_TYPES=file、LOG_FILE_PATH=/var/log/bunkerweb/ui.log;
  2. 后端站点内容:如本方案 ./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 安全加固指南》)。

十二、参考链接