文件
workbuddy-portal/Dockerfile
T
wangchuanli f36149efc3 feat(安全): 对外暴露面加固 + 界面去 AI 化(v1.5.0)
界面(去 AI 味):
- 大屏页清除 114 处生成器残留属性 data-page-node-id
- 视觉系统改回工程控制台风格:去 radial/linear-gradient、去辉光、
  去标题前彩色装饰条,改为中性灰阶 + 单一蓝色强调色;KPI 色条改状态点
- 精简各页说教式长提示;修掉 profile.html 泄漏到页面上的 Markdown 星号
- 删除登录页过时的「默认账号 admin / admin123」提示(1.4.0 起已无默认口令)

安全与隐私(按「将会被公网访问」收口):
- 内部异常只回 8 位事件号,完整堆栈进服务端日志(web/api.py::_internal)
- 导出文件名收敛:防响应头注入与路径穿越;manage.py passwd 补用户名校验
- 登录对不存在的账号也走一次哑哈希,抹平用户名枚举的时序差异
- /api/* 读接口限速 240 次 / 60 秒 / 账号(挡住循环调 /api/bundle)
- 进程 umask 0077 + 目录 0700 / 文件 0600:对话正文与主密钥的落盘权限
- 表名与库文件路径只对管理员下发;大屏页所有数据插值转义
- --debug 只允许绑定回环地址;新增 Permissions-Policy 与 413 处理器

文档:
- DEPLOYMENT 新增第十三节「安全与隐私基线」;迁移表补 1.4.0 → 1.5.0 行
- SECURITY 更新支持范围、新增「信息泄漏收敛」小节与上线检查项
- .codebuddy/ 加入 .gitignore(助手工作记忆不进仓库)

版本:1.4.0 → 1.5.0(无库结构变更,user_version 仍为 4)
验证:python tools/smoke.py → ok=264 fail=0;python tools/check_docs.py → 0 处问题
2026-09-18 11:13:17 +08:00

85 行
3.1 KiB
Docker

# syntax=docker/dockerfile:1
# =============================================================================
# workbuddy-portal —— 多阶段构建
# 构建层:只做「装依赖」,与源码解耦(改业务代码不会触发重装依赖)
# 运行层:slim 基础镜像 + 独立 venv + 非 root 用户
# =============================================================================
# ---------- Stage 1: 依赖 ----------
FROM python:3.13-slim AS builder
ENV PIP_NO_CACHE_DIR=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1
# 单独建 venv:运行时整目录拷过去,镜像里不留 pip 缓存与编译工具
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
WORKDIR /build
COPY requirements.txt ./
RUN pip install --upgrade pip \
&& pip install -r requirements.txt
# ---------- Stage 2: 运行 ----------
FROM python:3.13-slim AS runtime
LABEL org.opencontainers.image.title="WorkBuddy Portal" \
org.opencontainers.image.description="WorkBuddy 积分用量采集 / 存储 / 呈现一体化门户" \
org.opencontainers.image.version="1.5.0" \
org.opencontainers.image.source="https://git.iwali.top/wangchuanli/workbuddy-portal"
ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
PYTHONIOENCODING=utf-8 \
TZ=Asia/Shanghai \
PATH="/opt/venv/bin:$PATH" \
WB_DATA_DIR=/app/data \
WB_LOG_DIR=/app/logs \
WB_BACKUP_DIR=/app/backups \
WB_HOST=0.0.0.0 \
WB_PORT=8848
# tzdata:日志与「每日 09:00 / 17:00」的调度槽位都依赖本地时区
RUN set -eux; \
apt-get update; \
apt-get install -y --no-install-recommends tzdata ca-certificates; \
rm -rf /var/lib/apt/lists/*; \
ln -snf "/usr/share/zoneinfo/${TZ}" /etc/localtime; \
echo "${TZ}" > /etc/timezone; \
groupadd --gid 1000 app; \
useradd --uid 1000 --gid app --create-home --shell /usr/sbin/nologin app
COPY --from=builder /opt/venv /opt/venv
WORKDIR /app
# --chown 让非 root 用户能读写挂载卷之外的文件;.dockerignore 已挡掉数据与日志
COPY --chown=app:app . .
# 构建期断言:万一 .dockerignore 漏掉 backups/,在这里直接失败。
# 只靠「记得改 .dockerignore」不可靠 —— 备份归档里有 instance.json(含
# SECRET_KEY 与 cookie_key),进了镜像层就是整库密钥随镜像分发,且**无法**
# 用后续的 RUN rm 抹掉(只会多留一个含内容的只读层)。让构建自己拒绝。
RUN set -eux; \
if [ -n "$(ls -A /app/backups 2>/dev/null)" ]; then \
echo "ERROR: /app/backups 里有内容被打进镜像层 —— .dockerignore 漏了 backups/" >&2; \
exit 1; \
fi
RUN set -eux; \
chmod +x /app/docker/entrypoint.sh /app/docker/healthcheck.py; \
mkdir -p /app/data /app/logs /app/backups; \
chown -R app:app /app/data /app/logs /app/backups; \
python -c "import workbuddy_portal, flask, waitress; print('deps ok', flask.__version__)"
USER app
EXPOSE 8848
VOLUME ["/app/data", "/app/logs", "/app/backups"]
HEALTHCHECK --interval=30s --timeout=6s --start-period=20s --retries=3 \
CMD ["python", "/app/docker/healthcheck.py"]
ENTRYPOINT ["/app/docker/entrypoint.sh"]
CMD ["serve"]