# BookStack 容器化部署与多域名反代排错技术笔记

# BookStack 容器化部署与多域名反代排错技术笔记

## 1. 📝 问题与需求复盘

**核心需求**：
在内存受限（1GB RAM）的 VPS 环境下，通过 Docker Compose 部署 BookStack 知识库。配置主域名 `us1.w3lk.eu.org` 提供服务，并将辅域名 `st.w3lk.eu.org` 统一重定向至主域名，全程启用 HTTPS 加密。

**异常现象与排查过程**：

1. **初始异常（502 Bad Gateway）**：Nginx 配置正常，但访问报错。通过查看容器日志排查，发现 `bookstack` 容器输出 `The application key is missing, halting init!` 并中止运行。
2. **次生异常（500 Internal Server Error）**：补全密钥后访问报 500。日志抛出 `Access denied for user 'database_username'`，数据库连接被拒绝。

**根本原因 (Root Cause)**：

1. **502 报错根本原因**：BookStack 底层基于 Laravel 框架，强制要求配置唯一的 `APP_KEY` 用于敏感数据加密。若未提供此环境变量，容器安全机制将主动阻断启动过程。
2. **500 报错根本原因（双重叠加）**：
* **变量名版本弃用**：使用的 `linuxserver/bookstack` 最新镜像已弃用旧版简写环境变量（`DB_USER` / `DB_PASS`），要求使用全拼规范（`DB_USERNAME` / `DB_PASSWORD`）。因未正确识别变量，系统回退使用默认值 `database_username`。
* **本地卷脏数据污染 (Dirty Volume)**：错误配置在首次启动时被固化写入了宿主机映射的持久化目录（`bookstack_data` 和 `mariadb_data`）。仅修改 YAML 文件无法覆盖已生成的错误缓存，导致“无限报错”死循环。



---

## 2. 🔑 核心关键词与概念

* **`APP_KEY` (应用密钥)**：Laravel 框架的安全基石，一段 Base64 编码的随机字符串。用于加密用户 Session、Cookie 及数据库内的敏感信息。
* **卷污染 / 脏数据 (Volume Contamination)**：在 Docker 化部署中，带有状态的容器（如数据库或配置中心）一旦以错误参数完成初始化，该错误状态便会持久化在宿主机目录中。彻底排错必须经历“删库重建”的过程。
* **YAML 特殊字符转义**：在 `docker-compose.yml` 中，字符 `$` 被默认为环境变量调用符。若密码中包含 `$`，系统会将其解析为空值。最佳实践是避开该字符，或使用 `$$` 进行强转义。
* **单入口 301 重定向**：对于在配置中锁定了唯一 `APP_URL` 的系统（如 BookStack），直接绑定多个外部域名会导致静态资源加载跨域或前端排版错乱。运维标准做法是在 Nginx 代理层将辅域名执行 301 永久重定向至主域名。

---

## 3. 💻 关键命令行备忘

```bash
# ==========================================
# 密钥生成与容器排错
# ==========================================
# 1. 独立运行一次性容器，生成专属高强度 APP_KEY
docker run --rm --entrypoint /bin/bash lscr.io/linuxserver/bookstack:latest appkey

# 2. 追踪并输出所有相关容器的实时日志（排错核心视角）
docker compose logs -f

# 3. 彻底清理脏数据（⚠️ 仅限初始化失败、需推倒重来时执行）
docker compose down
rm -rf /opt/bookstack/mariadb_data /opt/bookstack/bookstack_data

# ==========================================
# 服务与网关管理
# ==========================================
# 4. 后台拉起并构建 Docker 容器
docker compose up -d

# 5. Nginx 配置语法检查（修改配置后必执行）
nginx -t

# 6. 无缝重载 Nginx 配置（不中断现有连接）
systemctl reload nginx

# 7. 为特定域名直接安装/应用已下载的 SSL 证书
certbot install --cert-name us1.w3lk.eu.org --nginx

```

---

## 4. 📄 核心配置文件范本

### 4.1 最终版 `docker-compose.yml`

```yaml
services:
  bookstack:
    image: lscr.io/linuxserver/bookstack:latest
    container_name: bookstack
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Asia/Singapore
      - APP_URL=https://us1.w3lk.eu.org
      - APP_KEY=base64:YOUR_GENERATED_KEY_HERE    # 💡【关键修改】必须提供合法的应用密钥
      - APP_LANG=zh_CN                            # 💡【可选增强】强制全局默认语言为简体中文
      - DB_HOST=bookstack_db
      - DB_USERNAME=bookstack                     # 💡【关键修改】符合新版镜像规范的全拼变量名
      - DB_PASSWORD=YourStrongPasswordHere        # 💡【关键修改】全拼规范，且密码已避开 $ 符号
      - DB_DATABASE=bookstackapp
    volumes:
      - ./bookstack_data:/config
    ports:
      - 127.0.0.1:6875:80
    restart: unless-stopped
    depends_on:
      - bookstack_db

  bookstack_db:
    image: lscr.io/linuxserver/mariadb:latest
    container_name: bookstack_db
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Asia/Singapore
      - MYSQL_ROOT_PASSWORD=YourRootPasswordHere
      - MYSQL_DATABASE=bookstackapp
      - MYSQL_USER=bookstack                      # 💡【约束点】必须与上方 DB_USERNAME 严格一致
      - MYSQL_PASSWORD=YourStrongPasswordHere     # 💡【约束点】必须与上方 DB_PASSWORD 严格一致
    volumes:
      - ./mariadb_data:/config
    restart: unless-stopped

```

### 4.2 Nginx 多域名反向代理配置 (`/etc/nginx/sites-available/bookstack`)

*(注：此为交给 Certbot 自动附加 SSL 配置前的基础逻辑架构)*

```nginx
# ==========================================
# 辅域名：执行统一重定向
# ==========================================
server {
    listen 80;
    listen [::]:80;
    server_name st.w3lk.eu.org;
    
    # 💡【关键修改】统一流量入口，防止单入口应用出现资源跨域
    return 301 https://us1.w3lk.eu.org$request_uri; 
}

# ==========================================
# 主域名：执行内网反向代理
# ==========================================
server {
    listen 80;
    listen [::]:80;
    server_name us1.w3lk.eu.org;

    location / {
        # 💡【关键修改】将外部公网流量转发至 Docker 映射的内部安全端口
        proxy_pass http://127.0.0.1:6875;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

```

---

## 5. ⚠️ 避坑指南与最佳实践

1. **时刻关注官方镜像环境变量变更**：开源镜像在迭代（特别是跨大版本）时，常会统一或更名环境变量（例如从简写走向规范全拼）。遭遇身份验证失败时，首要操作应是核对官方最新文档，避免刻舟求剑。
2. **初始化异常的“物理隔离”原则**：对于挂载了持久化卷（Volumes）的容器，若首次拉起因配置错误失败，单纯修改 `docker-compose.yml` 往往徒劳无功。**必须销毁旧容器并彻底删除对应的本地数据目录**，方能触发纯净的二次初始化流程。
3. **密码生成需防备 Shell 特殊字符**：在编排配置中，避免生成包含 `$` 符号的系统级密码。若不可避免，必须严格遵循 YAML 语法使用双美元符 `$$` 进行转义。推荐使用 `A-Z, a-z, 0-9, -, _, +` 字符集生成安全密码。
4. **日志是排错的唯一真理**：面对 502/500 这类宽泛的 HTTP 网关层报错，切忌盲目调试 Nginx。第一步永远应当下潜至服务层，执行 `docker compose logs -f` 定位真实错误锚点（如 `halting init` 或 `Access denied`）。