refactor: 镜像只装依赖,代码改为挂载
- Dockerfile: 去掉 COPY . .,镜像退化为纯依赖层,改代码不再需要重建镜像 - Dockerfile: 补 libgl1 / libxcb1 / libglib2.0-0 等 X11 库。opencv-python 在导入时需要它们,而 tools/utils.py 会经 slider_util 导入 cv2 —— 缺了不是某个边角功能挂掉,是整个应用起不来(已在真机冒烟中验证) - Dockerfile: 补 tzdata,否则 TZ=Asia/Shanghai 被静默忽略,所有时间戳落成 UTC - Dockerfile: apt 与 pip 均改走国内源。deb.debian.org 实测约 13 kB/s,96 MB 构建依赖要跑半小时以上 - docker-compose: 挂载 ./ 到 /app,部署流程从「重建镜像」变成「重传 + 重启」 - main.py: 启动时若缺 api/webui/index.html 就打印警告,避免只返回一段 JSON 却看着一切正常
This commit is contained in:
+33
-14
@@ -16,27 +16,46 @@ ENV PYTHONUNBUFFERED=1 \
|
|||||||
# asyncmy compiles a Cython extension, so a toolchain has to exist at build time.
|
# asyncmy compiles a Cython extension, so a toolchain has to exist at build time.
|
||||||
# It is left installed: purging it risks taking libmysqlclient with it, and a
|
# It is left installed: purging it risks taking libmysqlclient with it, and a
|
||||||
# slightly larger image is cheaper than a runtime that fails months later.
|
# slightly larger image is cheaper than a runtime that fails months later.
|
||||||
RUN apt-get update \
|
#
|
||||||
&& apt-get install -y --no-install-recommends \
|
# deb.debian.org is effectively unusable from this network -- it was pulling the
|
||||||
build-essential pkg-config default-libmysqlclient-dev \
|
# 96 MB of build dependencies at roughly 13 kB/s, which puts a build at well over
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
# half an hour. Point apt at a domestic mirror; override APT_MIRROR when building
|
||||||
|
# from somewhere that does not need it.
|
||||||
|
#
|
||||||
|
# The libgl1/libxcb1/... group is not for a GUI: opencv-python links against X11
|
||||||
|
# at import time, and tools/utils.py reaches cv2 through slider_util, so without
|
||||||
|
# them the *application* fails to import, not just some image utility. tzdata is
|
||||||
|
# here because TZ=Asia/Shanghai is silently ignored without it, which would put
|
||||||
|
# every stored timestamp in UTC.
|
||||||
|
#
|
||||||
|
# The pip mirror is set for the same reason as the apt one: this host's route to
|
||||||
|
# the public index is slow.
|
||||||
|
ARG APT_MIRROR=mirrors.tuna.tsinghua.edu.cn
|
||||||
|
RUN set -eux; \
|
||||||
|
for f in /etc/apt/sources.list /etc/apt/sources.list.d/debian.sources; do \
|
||||||
|
if [ -f "$f" ]; then \
|
||||||
|
sed -i "s|deb.debian.org|${APT_MIRROR}|g; s|security.debian.org|${APT_MIRROR}|g" "$f"; \
|
||||||
|
fi; \
|
||||||
|
done; \
|
||||||
|
apt-get update; \
|
||||||
|
apt-get install -y --no-install-recommends \
|
||||||
|
build-essential pkg-config default-libmysqlclient-dev \
|
||||||
|
libgl1 libglib2.0-0 libsm6 libxext6 libxrender1 libxcb1 libgomp1 \
|
||||||
|
tzdata; \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Requirements first: this layer only rebuilds when the pins actually change.
|
# Requirements only -- this is the one layer that is expensive to build and
|
||||||
|
# changes rarely.
|
||||||
ARG PIP_INDEX=https://pypi.tuna.tsinghua.edu.cn/simple
|
ARG PIP_INDEX=https://pypi.tuna.tsinghua.edu.cn/simple
|
||||||
COPY requirements.txt ./
|
COPY requirements.txt ./
|
||||||
RUN pip install --no-cache-dir -i "$PIP_INDEX" -r requirements.txt
|
RUN pip install --no-cache-dir -i "$PIP_INDEX" -r requirements.txt
|
||||||
|
|
||||||
COPY . .
|
# The application code is deliberately NOT copied in. compose mounts it at /app,
|
||||||
|
# so a code change is "re-upload the tarball, restart the container" instead of
|
||||||
# The WebUI bundle lives at api/webui, which is gitignored -- it is built on the
|
# an image rebuild. Treat this image as the dependency layer and nothing else;
|
||||||
# workstation (`cd webui && npm run build`) and shipped inside the build context.
|
# rebuild it when, and only when, requirements.txt or this file changes.
|
||||||
# Without this check a missing bundle is invisible until someone opens the page
|
|
||||||
# and gets the bare JSON stub from serve_frontend().
|
|
||||||
RUN test -f api/webui/index.html \
|
|
||||||
|| (echo "ERROR: api/webui/index.html is missing. Run 'cd webui && npm run build' before building the image." >&2; exit 1)
|
|
||||||
|
|
||||||
EXPOSE 18051
|
EXPOSE 18051
|
||||||
|
|
||||||
# api.main reads MC_HOST / MC_PORT from the environment; compose supplies both.
|
# api.main reads MC_HOST / MC_PORT from the environment; compose supplies both.
|
||||||
|
|||||||
+10
@@ -70,6 +70,16 @@ async def lifespan(_app: FastAPI):
|
|||||||
|
|
||||||
await init_db()
|
await init_db()
|
||||||
|
|
||||||
|
# The WebUI bundle is gitignored and built separately, so a deployment that
|
||||||
|
# forgot it would otherwise come up looking healthy and serve a bare JSON
|
||||||
|
# stub at "/" -- worth one loud line at boot rather than a puzzled operator.
|
||||||
|
if not os.path.exists(os.path.join(WEBUI_DIR, "index.html")):
|
||||||
|
print(
|
||||||
|
"[综合采集平台] 警告:未找到前端产物 api/webui/index.html,"
|
||||||
|
"根路径只会返回一段 JSON。请先在 webui/ 下执行 npm run build。",
|
||||||
|
flush=True,
|
||||||
|
)
|
||||||
|
|
||||||
generated = await ensure_initial_credential()
|
generated = await ensure_initial_credential()
|
||||||
if generated:
|
if generated:
|
||||||
# Printed once, on the run that creates it. There is no unauthenticated
|
# Printed once, on the run that creates it. There is no unauthenticated
|
||||||
|
|||||||
+6
-3
@@ -19,6 +19,9 @@ services:
|
|||||||
TZ: Asia/Shanghai
|
TZ: Asia/Shanghai
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
# Per-run crawler output, read back by the monitor layer. Without this the
|
# The code is mounted rather than baked in, so shipping a change is
|
||||||
# data would live inside the container and vanish on every rebuild.
|
# "re-upload the tarball, restart" instead of an image rebuild. Only the
|
||||||
- ./data:/app/data
|
# dependencies live in the image, because those are the expensive part and
|
||||||
|
# they change rarely -- rebuild only when requirements.txt or the
|
||||||
|
# Dockerfile itself changes.
|
||||||
|
- ./:/app
|
||||||
|
|||||||
Reference in New Issue
Block a user