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)。
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 |
| db | Docker Hub 官方 mysql:8.0 | 与官方 compose 一致,环境变量建库建用户 |
💡 技术拓展:知识库类 PHP 应用的容器化常见三选一——官方预构建镜像(如 BookStack
linuxserver/bookstack)、官方源码自建镜像(LibreKB 属此类)、第三方社区镜像。自建镜像的好处是版本与vendor/依赖完全跟官方 tag 对齐,代价是每次升级都要重新构建。
三、前置条件
- 主机已装 Docker Engine 与 Compose v2+;
- 取一个未被
ss -ltn占用的 Web 端口(本例 8139)与空闲自定义网段(本例 10.89.58.0/24); - 下载官方源码包并解压:
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/ 源码目录
- 部署目录把源码与编排文件组织为
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/mysql | docker 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_PORT | 8080 | 8139 | app 服务对宿主发布的 Web 端口 |
DB_ROOT_PASS | rootsecret | q1w2e3r4 | MySQL root 密码(MYSQL_ROOT_PASSWORD) |
DB_NAME | librekb | librekb | 自动创建的库名(MYSQL_DATABASE) |
DB_USER | librekb | librekb | 应用数据库用户(MYSQL_USER) |
DB_PASS | secret | q1w2e3r4 | 应用数据库密码(MYSQL_PASSWORD) |
⚠️ 注意:
DB_USER/DB_PASS/DB_NAME必须与config.phpdocker 分支里的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。
十二、参考链接
- 官方仓库与 Dockerfile/compose:https://github.com/michaelstaake/LibreKB(v2.3.3 tag,2026-11-01 实查)
- 官方 README Getting Started/Upgrading 章节(安装六步与升级口径来源)
- 本专栏:《LibreKB 介绍与选型指南》(https://www.quzuo.net/document/0oRDysMn1EDG)
- 同组参考:《AmuseWiki Docker Compose 部署指南》(https://www.quzuo.net/document/L589z4XaHJmH)
- PHP 官方镜像文档:https://hub.docker.com/_/php ;MySQL 官方镜像文档:https://hub.docker.com/_/mysql