14. 用Docker Compose部署企业级Wiki(BookStack),避坑指南。

2026-08-12

先交代个背景。上个月我们部门搞知识库,一群人天天在钉钉里互相甩文档,一个月下来,文件重命名有七个版本,最离谱的是有人把「最终版」改成了「最终版2(打死不改).docx」。领导脸都绿了。

我主动请缨,说用 BookStack 搭个 Wiki。花了一下午,用 Docker Compose 一把梭,看着容器全部 Up,觉着自己简直就是 DevOps 天花板。结果第二天上班,同事打开页面,白屏。我一看日志,数据库连不上了。后来折腾了大半天,发现是我加了个 ports: "3307:3306" 映射时改坏了配置。

行吧,今天就把这些坑一个一个扒出来,给各位老铁省点下班时间。


坑一:默认配置表里装,数据库映射瞎改必炸

你上网随便一搜,找到的 docker-compose.yml 大多是这套路:

services:
  bookstack:
    image: lscr.io/linuxserver/bookstack:latest
    environment:
      - DB_HOST=bookstack_db
      - DB_DATABASE=bookstack
      - DB_USERNAME=bookstack
      - DB_PASSWORD=secret
    ports:
      - "80:80"
    volumes:
      - ./bookstack_data:/config
    depends_on:
      - bookstack_db

  bookstack_db:
    image: lscr.io/linuxserver/mariadb:latest
    environment:
      - MYSQL_ROOT_PASSWORD=secret
      - MYSQL_DATABASE=bookstack
    volumes:
      - ./db_data:/config

到这千万别手贱。想象一下,BookStack 应用是你的手机,数据库是 Wi-Fi 路由器。你只改了手机上的 Wi-Fi 名称,但路由器那边的密码还是原来的,它俩自然就连不上。同理,你光在 bookstack 服务里改 DB_HOSTDB_PORT 没用,必须保证 bookstack_db 这条 linksdepends_on 指向的网络名完全对得上。

避坑建议:容器间通信用内部服务名(即 bookstack_db),不要用 localhost,更不要随便在 db 服务上加 ports 映射把 3306 改成 3307 暴露到宿主机——你一映射,后端连接串里没跟着改,前端界面就给你表演“No connection”。


坑二:卷目录用相对路径,跑哪儿哪儿崩

有次我在自己笔记本上写好了 compose 文件,扔到服务器上,改了环境变量就 up -d。结果容器起来了,页面却一直报错“Filesystem permission denied”。查了半天,是我把宿主机路径写成了 ./bookstack_data,然后服务器上有个什么 cron 任务在根目录下执行了 docker-compose,导致路径全变了。

这就好比,你给同事留了个便条说“资料在桌子上”,结果他跑到别人工位找,当然找不到。卷映射必须用绝对路径,且授权给容器的用户要匹配。LinuxServer 镜像默认是用 PUID / PGID 来跑程序的,你需要:

environment:
  - PUID=1000
  - PGID=1000

宿主机上执行 id 命令看一下你的 uid,跟这里对上。不然你会发现,网页里上传图片显示成功,但 /config/uploads 目录下文件权限是 root,Web 容器根本写不进去。


坑三:端口占用,防火墙在你和快乐之间横插一脚

BookStack 默认监听 80 端口,我服务器上装了 Nginx,也占着 80。这不是世界末日,改个外网映射就行:

ports:
  - "8765:80"

然后浏览器输 http://服务器IP:8765,结果还是打不开。排查命令:ss -tlnp | grep 8765,防火墙没放行该端口的概率极高。安全组、ufw、firewalld,三座大山挨个翻。别嫌麻烦,这一步省不了,跟追女生一样,就差临门一脚。


坑四:重启后数据蒸发,肠子悔青

我之前图省事,把数据库卷挂了个内存盘 /dev/shm,以为重启机器会更快。结果一次机房断电,数据全没了。你如果还想用 Docker 里的 DB,务必将 bookstack_db/config/var/lib/mysql 路径映射到宿主机持久目录。

另外,BookStack 本身管理后台也支持“备份”功能,但那是应用层的,建议 mysqldump 定时备份 SQL 文件,再把 /config/uploads 一起打 tar。我在 crontab 里加了这么一行:

0 2 * * * docker exec bookstack_db mysqldump -u bookstack -psecret bookstack > /backup/wiki_$(date +\%F).sql

数据没了还能接受,账号密码和几千条员工文档没了,你试试,周一晨会就是你的欢送会。


坑五:时区、邮件、OAuth,坑坑致命

默认镜像时区是 UTC,你同事凌晨两点发的笔记,时间戳却显示早上八点。加 TZ=Asia/Shanghai 就好。邮件配置如果 SMTP 出问题,用户找回密码永远失败。这里说个最隐蔽的坑:BookStack 的 APP_URL 必须是你实际访问的地址,比如 https://wiki.example.com,否则所有链接(包括邮件里的)都会生成 localhost,点进去全是 404。

这话说起来简单,生产环境里多少人换过域名之后忘了改这个参数,然后发出去的邮件都是“localhost/login” —— 太真实了。你不想看到那种邮件,那就好好设置 APP_URL 吧。


最后的完整姿势

一段相对靠谱的 compose 参考(我目前线上在用的):

version: "3"
services:
  bookstack:
    image: lscr.io/linuxserver/bookstack:latest
    container_name: bookstack
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Asia/Shanghai
      - APP_URL=http://your-server-ip:8765
      - DB_HOST=bookstack_db
      - DB_PORT=3306
      - DB_DATABASE=bookstack
      - DB_USERNAME=bookstack
      - DB_PASSWORD=change_me
    ports:
      - "8765:80"
    volumes:
      - /data/bookstack/config:/config
    depends_on:
      - bookstack_db
    restart: unless-stopped

  bookstack_db:
    image: lscr.io/linuxserver/mariadb:latest
    container_name: bookstack_db
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Asia/Shanghai
      - MYSQL_ROOT_PASSWORD=change_me
      - MYSQL_DATABASE=bookstack
      - MYSQL_USER=bookstack
      - MYSQL_PASSWORD=change_me
    volumes:
      - /data/bookstack/db:/config
    restart: unless-stopped

先把服务跑起来,再认认真真改了密码和挂载路径,基本就不会翻车。等哪天你发现同事开始主动写文档了,就知道这趟折腾值了。

最后说句掏心窝的话,容器化部署这东西,看着是抄作业,实际是拼细节。如果你团队里没人专职搞运维,又不想到处救火,也可以考虑省心的现成方案——毕竟,工具是为人服务的,不是为折腾服务的。更多方案可访问 itfangan.com,里面挑一挑,没准能找到更适合你们团队的落地姿势。