LibreKB 2.3.3 Docker Compose 部署指南

LibreKB 2.3.3 Docker Compose 部署实测:官方 Dockerfile 自建应用+mysql:8.0 编排、/install 安装向导 curl 实录、分类/文章/附件初始化、持久化与环境变量表、等价 docker run、mysqldump 备份与 6 条排障实录(storage 权限/curl -F/CSRF/503)。

LibreKBLibreKB文档管理WikiDocs2026/11/01

LibreKB 2.3.3 Docker Compose 部署指南

目标:在单台 Linux 主机上用 Docker Compose 把官方 LibreKB 2.3.3 完整跑起来——构建应用、初始化 MySQL、走完 /install 安装向导、创建分类/文章/附件并逐项验证,含 6 条排障实录。全部命令与回显均为 2026-11-01 在 192.168.1.200 主机实测。

一、环境信息

  • 主机:Linux x86_64,内网地址 192.168.1.200
  • Docker:Docker version 29.9.2, build a82e528
  • Compose:Docker Compose version v5.6.1
  • 软件版本:LibreKB 2.3.3(官方 GitHub tag v2.3.3)
  • 应用运行时:php:8.4-apache 基础镜像(实测回显 PHP/8.4.27、Apache/2.4.69 (Debian))
  • 数据库:mysql:8.0(实测服务端版本 8.0.46)
  • 编排项目名:librekb-233-stack;Web 端口 8139;自定义网络 10.89.58.0/24
  • 数据持久化:命名卷 librekb-db-data(数据库)+ bind mount(应用源码与 storage)

二、镜像选型说明

LibreKB 官方不发布预构建镜像,仓库自带 Dockerfile 与 docker-compose.yml,官方 compose 的 app 服务即 build: .(用源码目录现场构建)。因此本部署沿用官方路线:

服务镜像来源说明
app本地构建 librekb-233-stack-app(基于官方 Dockerfile,基镜像 php:8.4-apache)官方 Dockerfile 装 pdo_mysql/mysqli/zip 扩展、启用 mod_rewrite、写入 uploads.ini(upload_max_filesize=12M、post_max_size=128M)、COPY . /var/www/html 并 chown www-data
dbDocker Hub 官方 mysql:8.0与官方 compose 一致,环境变量建库建用户

💡 技术拓展:知识库类 PHP 应用的容器化常见三选一——官方预构建镜像(如 BookStack linuxserver/bookstack)、官方源码自建镜像(LibreKB 属此类)、第三方社区镜像。自建镜像的好处是版本与 vendor/ 依赖完全跟官方 tag 对齐,代价是每次升级都要重新构建。

三、前置条件

  1. 主机已装 Docker Engine 与 Compose v2+;
  2. 取一个未被 ss -ltn 占用的 Web 端口(本例 8139)与空闲自定义网段(本例 10.89.58.0/24);
  3. 下载官方源码包并解压:
cd /opt/data/quzuo/tmp/librekb
wget -c https://github.com/michaelstaake/LibreKB/archive/refs/tags/v2.3.3.tar.gz -O LibreKB-2.3.3.tar.gz
tar xzf LibreKB-2.3.3.tar.gz    # 得到 LibreKB-2.3.3/ 源码目录
  1. 部署目录把源码与编排文件组织为 deploy/app(源码 + config.php)与 deploy/docker-compose.yml。

四、部署步骤

4.1 准备 config.php

官方要求 cp config.example.php config.php 后手填参数;该文件的构造函数带 /.dockerenv 探测分支,容器环境直接套用 Docker 默认值。本部署把 docker 分支改为指向 compose 内的数据库服务与对外地址(完整字段逐项解释见《LibreKB 2.3.3 配置参数详解》):

if (file_exists('/.dockerenv')) {
    $this->db_host = 'librekb-233-db';
    $this->db_user = 'librekb';
    $this->db_pass = 'q1w2e3r4';
    $this->db_name = 'librekb';
    $this->systemURL = 'http://192.168.1.200:8139/';
    $this->bypassRequirements = true;
}

4.2 编排文件

deploy/docker-compose.yml(在官方 compose 基础上补了显式项目名、固定容器名、健康检查等待与独立网段):

name: librekb-233-stack

services:
  app:
    build: ./app
    container_name: librekb-233-app
    ports:
      - "8139:80"
    volumes:
      - ./app:/var/www/html
    depends_on:
      db:
        condition: service_healthy
    restart: unless-stopped
    networks:
      - librekb-net

  db:
    image: mysql:8.0
    container_name: librekb-233-db
    environment:
      - MYSQL_ROOT_PASSWORD=q1w2e3r4
      - MYSQL_DATABASE=librekb
      - MYSQL_USER=librekb
      - MYSQL_PASSWORD=q1w2e3r4
    volumes:
      - librekb-db-data:/var/lib/mysql
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-pq1w2e3r4"]
      interval: 5s
      timeout: 5s
      retries: 20
    restart: unless-stopped
    networks:
      - librekb-net

networks:
  librekb-net:
    driver: bridge
    ipam:
      config:
        - subnet: 10.89.58.0/24

volumes:
  librekb-db-data:

4.3 启动与构建

$ docker compose up -d --build
 Image librekb-233-stack-app Building
#8 [ 2/10] RUN apt-get update && apt-get install -y     git     zip     unzip     libzip-dev     && docker-php-ext-install pdo_mysql mysqli zip
#8 CACHED
#14 [ 9/10] COPY . /var/www/html/
#14 DONE 0.3s
#15 [10/10] RUN chown -R www-data:www-data /var/www/html
#15 DONE 0.5s
#16 naming to docker.io/library/librekb-233-stack-app:latest done
 Image librekb-233-stack-app Built
 Volume librekb-233-stack_librekb-db-data Created
 Network librekb-233-stack_librekb-net Created
 Container librekb-233-db Creating
 Container librekb-233-db Created
 Container librekb-233-app Creating
 Container librekb-233-app Created
 Container librekb-233-db Starting
 Container librekb-233-db Started
 Container librekb-233-db Waiting
 Container librekb-233-db Healthy
 Container librekb-233-app Starting
 Container librekb-233-app Started

$ docker compose ps
NAME              IMAGE                   COMMAND                  SERVICE   CREATED         STATUS                   PORTS
librekb-233-app   librekb-233-stack-app   "docker-php-entrypoi…"   app       6 seconds ago   Up Less than a second    0.0.0.0:8139->80/tcp, [::]:8139->80/tcp
librekb-233-db    mysql:8.0               "docker-entrypoint.s…"   db        6 seconds ago   Up 5 seconds (healthy)   3306/tcp, 33060/tcp

depends_on.condition: service_healthy 保证 app 一定等 MySQL 就绪才启动,避免首启连不上库。

4.4 运行安装向导

LibreKB 用 /install 网页安装器建表并创建管理员。表单含 session 级 csrf_token,命令行等价操作实测:

$ curl -s -c cookies.txt -o install.html -w "GET /install -> HTTP %{http_code}\n" http://192.168.1.200:8139/install
GET /install -> HTTP 200
<title>LibreKB Installer</title>

$ curl -s -b cookies.txt -c cookies.txt -o /dev/null \
    -w "POST /install -> HTTP %{http_code} redirect=%{redirect_url}\n" \
    http://192.168.1.200:8139/install \
    --data-urlencode "csrf_token=<install.html 中取出的 64 位 token>" \
    --data-urlencode "email=admin@kb.example.com" \
    --data-urlencode "password=q1w2e3r4"
POST /install -> HTTP 302 redirect=http://192.168.1.200:8139/admin

$ curl -s -o /dev/null -w "GET /install -> HTTP %{http_code}\n" http://192.168.1.200:8139/install
GET /install -> HTTP 404

安装器建出 6 张表并写入管理员(安装后 /install 永久 404,属正常保护):

$ docker exec librekb-233-db mysql -ulibrekb -pq1w2e3r4 librekb -e "SHOW TABLES; SELECT id,email,\`group\`,status FROM users;"
Tables_in_librekb
articles
attachments
categories
log
settings
users
id      email                   group   status
1       admin@kb.example.com    admin   enabled

4.5 登录与内容初始化实测

登录后创建分类(含嵌套与禁用态)、文章(含附件),逐项回显:

$ curl -s -b cookies.txt -c cookies.txt -w "POST /login -> HTTP %{http_code} redirect=%{redirect_url}\n" \
    http://192.168.1.200:8139/login --data-urlencode "csrf_token=ea5b4f528707..." \
    --data-urlencode "email=admin@kb.example.com" --data-urlencode "password=q1w2e3r4"
POST /login -> HTTP 302 redirect=http://192.168.1.200:8139/admin
$ curl -s -b cookies.txt -c cookies.txt -o dashboard.html -w "GET /admin -> HTTP %{http_code}\n" http://192.168.1.200:8139/admin
GET /admin -> HTTP 200
<title>Dashboard - LibreKB</title>

$ curl -s -b cookies.txt -c cookies.txt -w "POST /admin/categories/create -> HTTP %{http_code} redirect=%{redirect_url}\n" \
    http://192.168.1.200:8139/admin/categories/create --data-urlencode "csrf_token=..." \
    --data-urlencode "name=Getting Started" --data-urlencode "description=First steps with the knowledge base." \
    --data-urlencode "icon=book" --data-urlencode "parent=" --data-urlencode "status=enabled" --data-urlencode "order=1"
POST /admin/categories/create -> HTTP 302 redirect=http://192.168.1.200:8139/admin

带附件的文章创建用 multipart(非文件字段一律 --form-string,见「常见问题」6.2):

$ curl -s -b cookies.txt -c cookies.txt -w "POST art1 -> HTTP %{http_code} redirect=%{redirect_url}\n" \
    http://192.168.1.200:8139/admin/articles/create \
    --form-string "csrf_token=..." --form-string "title=Reset Your Password" \
    --form-string "content=<p>Use the <strong>Forgot password</strong> link on the login page. A reset email is sent to your address.</p>" \
    --form-string "category=1" --form-string "status=enabled" --form-string "order=1" \
    -F "attachments[]=@att-demo.txt;type=text/plain"
POST art1 -> HTTP 302 redirect=http://192.168.1.200:8139/admin

$ docker exec librekb-233-db mysql -ulibrekb -pq1w2e3r4 librekb -e "SELECT id,article_id,original_name,size,mime_type FROM attachments;"
id      article_id      original_name   size    mime_type
1       1               att-demo.txt    64      text/plain

前台与附件下载验证:

$ curl -s http://192.168.1.200:8139/ | grep -oE '<title>[^<]*</title>'
<title>Categories - LibreKB Demo</title>
$ curl -s -o /dev/null -w "GET /category/getting-started -> %{http_code}\n" http://192.168.1.200:8139/category/getting-started
GET /category/getting-started -> 200
$ curl -s -D - -o att-download.txt http://192.168.1.200:8139/attachment/1 | head -3
HTTP/1.1 200 OK
Content-Length: 64
Content-Disposition: attachment; filename="att-demo.txt"

五、数据持久化说明

数据载体位置说明
MySQL 数据目录命名卷 librekb-db-data/var/lib/mysqldocker compose down 不带 --volumes 时保留
应用源码bind mount ./app:/var/www/html部署目录官方 compose 即此形态,改代码免重建镜像
附件文件同上(源码内)app/storage/uploads/attachments/随机 32 位十六进制文件名,目录内程序自动写 Require all denied 的 .htaccess
HTML Purifier 缓存同上app/storage/cache/htmlpurifier/富文本清洗器缓存

实测细节:docker compose down --volumes 清库重建后,storage/uploads/attachments/ 里上一轮上传的 45a8727a950ea4922335d3916971662a.txt 仍在——bind mount 的附件文件不随数据库卷删除,备份必须「库 + storage 目录」成对做,否则会出现库无记录、盘有孤文件的悬挂附件。

六、环境变量表

官方 docker-compose.yml 支持以下变量(本部署为固定值写法,等价于全部显式赋值):

变量官方默认本部署取值作用
APP_PORT80808139app 服务对宿主发布的 Web 端口
DB_ROOT_PASSrootsecretq1w2e3r4MySQL root 密码(MYSQL_ROOT_PASSWORD)
DB_NAMElibrekblibrekb自动创建的库名(MYSQL_DATABASE)
DB_USERlibrekblibrekb应用数据库用户(MYSQL_USER)
DB_PASSsecretq1w2e3r4应用数据库密码(MYSQL_PASSWORD)

⚠️ 注意:DB_USER/DB_PASS/DB_NAME 必须与 config.php docker 分支里的 db_user/db_pass/db_name 一致——compose 环境变量只作用于 MySQL 镜像初始化,LibreKB 应用不读这些环境变量。

七、配置参数说明

应用侧配置只有 config.php 一个文件(数据库、systemURL、SMTP 邮件、bypassRequirements),站点级配置全部存 settings 表并在后台五分区(General/Appearance/Editor/API/Maintenance)修改。安装后 settings 表初始 12 键实测:

$ docker exec librekb-233-db mysql -ulibrekb -pq1w2e3r4 librekb -e "SELECT name,value FROM settings ORDER BY name;"
name                 value
api_cors_origin      *
api_enabled          disabled
api_key
custom_css
editor_toolbar
google_fonts_css_url
kb_visibility        public
maintenance_message
maintenance_mode
site_color
site_logo
site_name

逐字段含义、取值规则与生产模板见《LibreKB 2.3.3 配置参数详解》。

八、配置文件挂载

bind mount ./app:/var/www/html 使 config.php 直接以宿主文件生效:

$ ls /opt/data/quzuo/tmp/librekb/deploy/app/config.php
/opt/data/quzuo/tmp/librekb/deploy/app/config.php

修改 config.php 后无需重新构建,重载应用进程即可(PHP 脚本随请求读取)。systemURL 决定页面资源与 API 附件 url 字段的绝对地址前缀,改端口/域名后必须同步。

九、等价 docker run

与 4.2 节 compose 等价的逐条命令(网络、数据库、应用):

docker network create --driver bridge --subnet 10.89.58.0/24 librekb-net

docker run -d --name librekb-233-db --network librekb-net \
  -e MYSQL_ROOT_PASSWORD=q1w2e3r4 -e MYSQL_DATABASE=librekb \
  -e MYSQL_USER=librekb -e MYSQL_PASSWORD=q1w2e3r4 \
  -v librekb-db-data:/var/lib/mysql \
  --restart unless-stopped mysql:8.0

docker build -t librekb-233-stack-app ./app

docker run -d --name librekb-233-app --network librekb-net \
  -p 8139:80 -v /opt/data/quzuo/tmp/librekb/deploy/app:/var/www/html \
  --restart unless-stopped librekb-233-stack-app

十、常用运维命令

docker compose ps                          # 查看服务状态
docker compose logs -f app                 # 跟踪 Apache 访问日志(实测含每条请求回显)
docker compose logs db                     # MySQL 启动日志(实测 ready for connections 行)
docker compose restart app                 # 重载应用
docker compose down                        # 停止并删除容器/网络,保留数据卷
docker compose down --volumes              # 连数据库卷一起删(附件文件在 bind mount 中不会删)

数据库备份实测:

$ docker exec librekb-233-db mysqldump -ulibrekb -pq1w2e3r4 librekb > backup-test.sql
$ ls -l backup-test.sql
-rw-r--r-- 1 root root 9928 Nov  1 23:55 backup-test.sql
$ head -3 backup-test.sql
-- MySQL dump 10.13  Distrib 8.0.46, for Linux (x86_64)
--
-- Host: localhost    Database: librekb

恢复:docker exec -i librekb-233-db mysql -ulibrekb -pq1w2e3r4 librekb < backup-test.sql,同时把 storage/uploads/ 目录一并还原。

十一、常见问题

11.1 上传附件报「The upload directory is not writable」

实测现场:文章保存成功但页面提示 Article saved, but some attachments were not added: att-demo.txt: The upload directory is not writable.。根因:bind mount 的宿主 storage/ 目录属 root,镜像内 chown www-data 只作用于 COPY 进镜像的副本,挂载后即被宿主目录覆盖。修复(宿主执行):

$ chown -R 33:33 /opt/data/quzuo/tmp/librekb/deploy/app/storage
$ ls -ld /opt/data/quzuo/tmp/librekb/deploy/app/storage
drwxrwxr-x 4 www-data www-data 4096 Oct 31 02:11 /opt/data/quzuo/tmp/librekb/deploy/app/storage

之后重新上传实测通过:/attachment/1 返回 HTTP/1.1 200 OK、Content-Disposition: attachment; filename="att-demo.txt",内容逐字节一致。裸机部署同理——README 明确要求 PHP 对 storage/uploads、storage/cache 可写。

11.2 curl -F 上传正文时 HTTP 000

实测现场:curl -F "content=<p>Use the <strong>...</p>" 直接 POST art1 -> HTTP 000(请求根本没发出去)。根因:curl 的 -F 语法会把字段值里的 < 解释为「从文件读取内容」。修复:非文件字段一律改用 --form-string(不解释 </@/;),只有真文件参数保留 -F "attachments[]=@att-demo.txt;type=text/plain"。改后 302 成功。

11.3 后台 POST 全部 403

LibreKB 所有表单带 session 级 csrf_token 隐藏域。脚本化操作必须先 GET 目标表单页取 token 再 POST;拿已登出/过期会话的 token 提交会 403。实测正确流程见 4.4/4.5 节(-b/-c 同一 cookie jar 贯穿)。

11.4 装完后 /install 返回 404

正常保护行为:users 表非空后安装器即失效(404 而非报错页)。重装需先清空数据库(docker compose down --volumes)或手工删用户表。

11.5 API 一调用就 503

{"error":"The API is disabled."}——API 默认关闭(settings.api_enabled=disabled),需在后台 Settings→API 开启,实测见《LibreKB 2.3.3 API 使用详解》。

11.6 首启页面报数据库连接失败

若去掉了 depends_on.condition: service_healthy,MySQL 初始化期间 app 已可接收请求,/install 会显示 Database connection failed。官方 compose 只写了 depends_on: - db(无健康条件),生产编排建议按本指南补 healthcheck。

十二、参考链接

LibreKB 2.3.3 Docker Compose 部署指南 - Quzuo