bigywater.tech
Flask · 178

Docker 部署 Flask

把 Flask 放进容器,Dockerfile 里有几处写法决定了镜像大小、构建速度、能不能被访问和日志什么时候出现。本机拉不到 python 基础镜像,所以这篇分两部分验证:与基础镜像无关的构建机制用 Docker Desktop 实际构建(FROM scratch);依赖安装、绑定地址、日志缓冲、权限这些则直接在本机用 Flask 和 gunicorn 做同样的事。需要说明:完整的镜像没有构建过,docker run 也没有执行过。

52.44MB → 161B
.dockerignore 前后发给构建器的上下文大小
1.5 → 0.0 MB
pip 缓存目录:默认 vs --no-cache-dir
ConnectionRefused
gunicorn 只绑 127.0.0.1 时经外部地址访问的结果

构建上下文与层缓存

实测 · 本机执行输出
  没有 .dockerignore:退出码 0,上下文 52.44MB
  有 .dockerignore(venv、.git、__pycache__):退出码 0,上下文 161B
  镜像大小:没有 .dockerignore 104.9 MB,有 0.026 MB(COPY . 把 venv 与 .git 都装进了镜像)

上一篇:媒体文件生产部署;下一篇:健康检查接口。

前 4 节的构建以 FROM scratch 为基础,验证的是构建机制;后 4 节在本机直接用 Flask 与 gunicorn 验证。

.dockerignore 的作用

实测 · 本机执行输出
  [A:先 COPY .,再 COPY requirements.txt] 只改了 app.py 之后的 COPY 步骤:
     COPY . /app                                        built
     COPY requirements.txt /app/requirements.txt        built
  [B:先 COPY requirements.txt,再 COPY app.py] 只改了 app.py 之后的 COPY 步骤:
     COPY requirements.txt /app/requirements.txt        CACHED
     COPY app.py /app/app.py                            built

shell 形式与 exec 形式的 CMD

实测 · 本机执行输出
  shell 形式 CMD gunicorn ...        镜像里的 Cmd = ["/bin/sh","-c","gunicorn -b 0.0.0.0:8000 app:app"]
  exec 形式 CMD ["gunicorn", ...]    镜像里的 Cmd = ["gunicorn","-b","0.0.0.0:8000","app:app"]

下面是按本篇验证过的规则写的 Flask Dockerfile 的常见形状(这个完整的 Dockerfile 没有被构建过,因为拉不到 python:3.14-slim;每一步的效果在本篇里分别验证过):

FROM python:3.14-slim
ENV PYTHONUNBUFFERED=1
WORKDIR /app
RUN useradd --create-home app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
USER app
EXPOSE 8000
CMD ["gunicorn", "-b", "0.0.0.0:8000", "app:app"]

依赖在源码之前(层缓存)、pip 不留缓存、PYTHONUNBUFFERED、非 root、exec 形式 CMD、绑定 0.0.0.0。

docker build --check

实测 · 本机执行输出
  退出码 1;提示:
    WARNING: JSONArgsRecommended - https://docs.docker.com/go/dockerfile/rule/json-args-recommended/
    JSON arguments recommended for CMD to prevent unintended behavior related to OS signals
    WARNING: MaintainerDeprecated - https://docs.docker.com/go/dockerfile/rule/maintainer-deprecated/
    Maintainer instruction is deprecated in favor of using label
    WARNING: current commit information was not captured by the build: failed to read current commit information with git rev-parse --is-inside-work-tree

pip install

实测 · 本机执行输出
  pip install(默认,有缓存)       退出码 0,用时 23.0 s,装了 8 个包,venv 18.8 MB,pip 缓存目录 1.5 MB
  pip install --no-cache-dir退出码 0,用时 17.7 s,装了 8 个包,venv 18.8 MB,pip 缓存目录 0.0 MB
  装好的包: blinker==1.9.0, click==8.5.0, Flask==3.1.3, gunicorn==26.2.0, itsdangerous==2.2.0, Jinja2==3.1.6, MarkupSafe==3.0.3, Werkzeug==3.1.9

绑定地址

实测 · 本机执行输出
  -b 0.0.0.0:PORT  →  经 127.0.0.1 访问:200 ok;经局域网地址访问:200 ok
  -b 127.0.0.1:PORT  →  经 127.0.0.1 访问:200 ok;经局域网地址访问:ConnectionRefusedError
  (容器里「外部」访问走的是容器自己的网卡地址,不是容器内的 127.0.0.1;这里的局域网地址是同样的道理,没有用到 Docker 网络)

日志缓冲

实测 · 本机执行输出
  默认                  请求后 1.5 秒日志里有那行:False;进程被 SIGTERM 退出之后有:True
  PYTHONUNBUFFERED=1  请求后 1.5 秒日志里有那行:True;进程被 SIGTERM 退出之后有:True

只读目录

实测 · 本机执行输出
  目录可写            → 200 written
  目录只读(a-w)       → 500 PermissionError
  (这是用权限位模拟,不是真的换了一个用户;root 不受权限位限制,所以容器里用 root 时这个问题看不到)

分步演示

Docker 部署 Flask
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Python 3.14.7 / Flask 3.1.3 / Werkzeug 3.1.9 / Jinja2 3.1.6 / SQLAlchemy 2.1.1 / gunicorn 26.2.0(都装在自己的虚拟环境里);大多数实验用 Flask 的 test_client(直接调用 WSGI 应用,不经过网络),需要真实 HTTP 时另起 gunicorn 或开发服务器(端口 9600 以上,本机 127.0.0.1);计时用 time.perf_counter,数字每次运行会有小幅抖动。Docker 29.8.0(Docker Desktop),构建只用 FROM scratch;本机 Python 3.14、Flask 3.1.3、gunicorn 26.2.0
    • 没有验证:用 python 基础镜像构建完整的 Flask 镜像、docker run 与端口映射、真的换成非 root 用户(只用 chmod 模拟)、useradd 与文件属主、HEALTHCHECK、多阶段构建、镜像大小优化(slim/alpine 对比)、compose 与 K8s 编排、容器里 gunicorn 的信号(信号处理见第 189 篇)
    京ICP备2026067502号-1京公网安备11010502063487号