Docker基础入门:从环境一致性到生产就绪镜像构建

1. 这不是“学个命令就完事”的 Docker 入门——它是一次对软件交付逻辑的重新理解

很多人点开“Docker基础入门”这个标题,心里想的是:“哦,又一个教 pull、run、build 的教程。”结果学完发现,自己还是写不好 Dockerfile,容器一上生产就崩,镜像体积大得离谱,团队协作时别人拉不动你的镜像,CI/CD 流水线卡在构建阶段半天没反应……这不是 Docker 不好用,而是从一开始,我们就把它当成了“另一个 Linux 命令”,而没意识到:Docker 是一套全新的软件交付契约——它把“这堆代码在你机器上能跑”,变成了“这堆代码在任何符合标准的环境里,都必须以完全一致的方式运行”。

我带过不下二十个跨行业项目组,从某高校实验室的图像处理 Demo,到某公司内部的报表生成服务,再到某跨平台 IoT 设备管理后台,凡是把 Docker 当成“高级 tar 包”的团队,三个月内必踩三类坑:一是本地开发环境和测试环境行为不一致(比如时区错乱、glibc 版本冲突、字体缺失导致 PDF 渲染失败);二是运维同学拿到镜像后根本不敢直接上线,非要先解包看 Dockerfile 里写了啥;三是 CI 构建耗时从 3 分钟飙升到 18 分钟,只因为某位同事在 RUN 指令里顺手 apt update && apt install -y 了一堆调试工具。

所以这篇“Docker基础入门”,不讲“Docker 是什么”这种教科书定义,也不堆砌 docker --help 的输出。我们直接从一个真实场景切入:你刚接手一个 Python Flask 写的 API 服务,代码仓库里只有 requirements.txt 和 app.py,前任留了句“本地用 pip install 跑就行”,但你现在要把它部署到三台不同配置的服务器上,并确保每次更新后所有节点行为完全一致——Docker 不是让你多打几个命令,而是帮你把“环境一致性”这件事,从人肉校验,变成可验证、可版本化、可自动化的工程事实。

核心关键词“Docker基础入门”背后,真正要解决的,从来不是“怎么启动容器”,而是:

  • 如何让开发、测试、运维三方对“运行环境”达成无歧义共识?
  • 如何把“我这能跑”这句话,转化成一行可执行、可审计、可回滚的声明?
  • 当业务逻辑没变,只是换了个服务器型号或操作系统小版本,为什么服务就突然 502 了?Docker 怎么提前把这个风险锁死?

如果你正被这类问题困扰,或者你已经会 run 两个容器但总觉得“差点意思”,那这篇内容就是为你写的。它不假设你懂 Linux 容器原理,但默认你写过代码、配过服务器、被环境问题背刺过至少一次。接下来的所有内容,都围绕一个目标展开:让你第一次写完 Dockerfile,就能产出一个别人拿过去不用改、不用猜、不用查文档就能直接上线的镜像。


2. 为什么非得用 Docker?——从“环境漂移”到“声明式交付”的底层逻辑

2.1 环境漂移:那个没人敢动的“线上服务器”

先说个真实案例。某公司内部有个数据清洗脚本,用 Python + Pandas 写的,功能很简单:读 Excel,去重,导出 CSV。开发在 macOS 上用 Python 3.9 + pandas 1.5.3 跑得好好的;测试在 Ubuntu 20.04 上装了系统自带的 Python 3.8,发现 pandas 报ImportError: cannot import name 'ABCIndexClass';运维上线时用了 CentOS 7,默认 Python 2.7,连 pip 都没装……最后解决方案是:运维手动在三台服务器上分别 pip install 指定版本,再加个 shell 脚本检查依赖,每次更新还得人工 diff requirements.txt 和线上实际安装的包。

这就是典型的“环境漂移”(Environment Drift):同一份代码,在不同机器上,因操作系统、内核版本、库文件路径、甚至时区设置的细微差异,导致行为不可预测。传统方案靠“文档说明”“口头约定”“运维 checklist”来对抗,但这些全是人肉防火墙,漏一个环节就全盘失效。

提示:环境漂移不是“小问题”。它直接导致:

  • 故障复现困难(“我本地没问题啊!”)
  • 发布周期拉长(每次上线前要花 2 小时确认环境)
  • 团队信任损耗(开发怪运维环境不对,运维怪开发没写清楚)
  • 技术债滚雪球(没人敢升级基础镜像,怕牵一发而动全身)

2.2 Docker 的解法:把“环境”变成“可版本化的文件”

Docker 不是发明了新东西,而是把 Linux 已有的 cgroups(资源限制)、namespaces(进程隔离)、overlayfs(分层文件系统)等内核能力,封装成一套开发者友好的交付协议。它的核心思想非常朴素:“运行环境”不该是服务器上一堆看不见摸不着的配置,而应该是一份明文的、可 Git 管理的、能一键重建的描述文件。

这个描述文件就是 Dockerfile。它不是脚本,而是声明式合约。你写FROM python:3.9-slim,不是告诉 Docker “去下载一个镜像”,而是声明:“我的应用,必须运行在一个以 Debian 11 为基础、预装 Python 3.9.16、且不含 vim/telnet 等调试工具的最小化环境中”。Docker 引擎会严格按此声明构建,如果某步操作破坏了这个前提(比如 RUN apt install -y gcc 导致镜像变大、引入不必要依赖),那它就违背了合约——而这恰恰是你需要主动发现的风险点。

对比传统方式:

维度传统部署方式Docker 方式
环境定义口头约定 / Word 文档 / 运维笔记Dockerfile(纯文本,Git 可追溯)
环境构建运维手工执行命令 / Ansible Playbookdocker build(全自动,无交互,可重复)
环境验证上线前人工检查 / 临时写脚本验证docker run -it 镜像名 /bin/sh(秒级验证)
版本管理依赖服务器快照 / 手动备份配置文件镜像 ID + tag(如 myapp:v1.2.0)
交付物代码包 + 配置文件 + 依赖清单文档单个镜像文件(含代码、依赖、运行时、配置)

关键点在于:Docker 把“环境”从“状态”(state)变成了“快照”(snapshot)。你不再问“这台服务器装了啥”,而是问“这个镜像基于哪个基础层构建”。而镜像的每一层,都对应 Dockerfile 中的一条指令,且只读不可变——这就彻底切断了“环境漂移”的物理路径。

2.3 为什么是“基础入门”,而不是“快速上手”?

很多教程一上来就教你docker run -d -p 8080:80 nginx,这就像教人开车先让踩油门却不讲离合和档位。Docker 的“基础”,不在命令语法,而在三个必须建立的认知锚点:

  1. 镜像是分层的,且层是只读的
    FROM ubuntu:22.04是第一层,RUN apt update && apt install -y python3是第二层,COPY . /app是第三层。每一层都是前一层的增量变更,且一旦构建完成就不可修改。这意味着:

    • 你不能在容器运行时apt install新包(改的是可写层,重启即丢);
    • 多个镜像共享相同基础层(如都用python:3.9-slim),磁盘只存一份;
    • docker build会缓存中间层,如果某行COPY的文件没变,后续所有层直接复用缓存。
  2. 容器是进程,不是虚拟机
    docker run启动的不是一个“迷你操作系统”,而是直接运行在宿主机内核上的一个进程(PID=1)。它没有自己的 init 系统、没有独立的内核、不模拟硬件。所以:

    • 容器内ps aux看到的进程,就是宿主机上真实存在的进程;
    • 容器崩溃 = 进程退出,没有“蓝屏”概念;
    • 容器间通信走 localhost 是走环回接口,不是网络栈。
  3. Docker daemon 是中心枢纽,不是透明代理
    所有docker命令(除了docker buildx等少数例外)最终都通过 Unix Socket(Linux)或 Named Pipe(Windows)与本地dockerd进程通信。这意味着:

    • 你无法用远程 Docker CLI 直接操作另一台服务器的容器(除非显式配置 TCP socket,且极不安全);
    • docker context是为了解决多环境切换问题,而非绕过 daemon;
    • docker-compose本质是调用 daemon API 的 YAML 封装器,不是独立运行时。

这三个认知,决定了你后续写 Dockerfile、排障、设计 CI 流程的底层逻辑。跳过它们,后面所有操作都是空中楼阁。


3. 从零开始:一个真实可用的 Python Web 服务 Dockerfile 实战

3.1 场景还原:我们到底要容器化什么?

假设你接手的项目目录结构如下:

my-flask-app/ ├── app.py # 主程序 ├── requirements.txt # 依赖列表 ├── static/ # 静态文件 ├── templates/ # Jinja2 模板 └── config.py # 配置文件(含 SECRET_KEY)

app.py内容极简:

from flask import Flask import os app = Flask(__name__) app.config.from_object('config') @app.route('/') def hello(): return f"Hello from {os.getenv('HOSTNAME', 'unknown')}!"

requirements.txt:

Flask==2.2.5 gunicorn==21.2.0

config.py:

import os SECRET_KEY = os.getenv('SECRET_KEY', 'dev-key')

目标:构建一个镜像,运行后访问http://localhost:8000返回"Hello from <容器ID>!",且满足:

  • 镜像体积 ≤ 120MB;
  • 不包含任何调试工具(如 bash、vim、curl);
  • 支持通过环境变量注入SECRET_KEY;
  • 使用 gunicorn 作为 WSGI 服务器,非 Flask 自带的开发服务器。

3.2 Dockerfile 编写:每一步都在回答一个关键问题

我们逐行写 Dockerfile,并解释每个选择背后的“为什么”:

# 第1行:选择基础镜像 —— 为什么是 python:3.9-slim,而不是 latest 或 alpine? FROM python:3.9-slim # 第2行:创建非 root 用户 —— 为什么必须做?root 容器是生产环境红线 RUN groupadd -g 1001 -f appuser && \ useradd -s /bin/bash -u 1001 -m appuser # 第3行:设置工作目录 —— 为什么用 /app 而不是 /home/appuser? WORKDIR /app # 第4行:复制依赖文件并安装 —— 为什么先 COPY requirements.txt 再 COPY . ? COPY requirements.txt ./ RUN pip install --no-cache-dir -r requirements.txt # 第5行:复制应用代码 —— 为什么放在最后?这是体积优化的核心 COPY . . # 第6行:切换用户 —— 为什么必须在 COPY 之后、CMD 之前? USER appuser # 第7行:暴露端口 —— EXPOSE 是文档性声明,不是防火墙规则 EXPOSE 8000 # 第8行:启动命令 —— 为什么用 gunicorn 而不是 python app.py? CMD ["gunicorn", "--bind", "0.0.0.0:8000", "--workers", "2", "app:app"]

现在,我们拆解每一行的深层逻辑:

3.2.1FROM python:3.9-slim:基础镜像选型的硬核计算

python:3.9-slim是 Debian 11 + Python 3.9.16 的精简版,大小约 125MB。对比其他选项:

镜像标签大小(实测)特点
python:3.9~950MB包含完整 Debian 系统、编译工具链(gcc, make)、文档、调试符号
python:3.9-slim~125MB移除 man pages、dev headers、perl、git 等,保留 apt-get,适合生产
python:3.9-alpine~55MB基于 Alpine Linux(musl libc),但部分 Python 包(如 numpy)需编译,易出错

计算依据:

  • 我们的应用不需要编译 C 扩展(Flask/gunicorn 都是纯 Python 或预编译 wheel),所以slim足够;
  • Alpine 的 musl libc 与 glibc 不兼容,某些二进制依赖(如 psycopg2-binary)可能运行异常,slim基于 glibc,兼容性 100%;
  • latest标签是浮动的,今天构建是 3.9,明天可能是 3.10,违反“可重现性”原则,必须锁定小版本。

实操心得:永远用python:X.Y-slim,除非你明确需要 Alpine 的极致轻量且已验证所有依赖兼容。slim是生产环境的黄金标准。

3.2.2RUN groupadd && useradd:安全不是可选项,是构建时的强制动作

Docker 默认以 root 用户运行容器内所有进程。这意味着:

  • 如果应用存在 RCE 漏洞,攻击者直接获得 root 权限;
  • 容器内进程可任意读写/etc/passwd、挂载宿主机目录(若用了-v);
  • 某些 Kubernetes 安全策略(如 PodSecurityPolicy)会拒绝 root 容器。

创建appuser并赋予 UID 1001,是为了:

  • 符合 Linux 最佳实践(UID 1001 是普通用户起始值);
  • 与 Kubernetes 的runAsNonRoot: true策略天然兼容;
  • 避免因 UID 冲突导致挂载卷权限错误(宿主机 UID 1001 的文件,容器内appuser可直接读写)。

注意:useradd -s /bin/bash是为了调试方便(docker exec -it 容器名 /bin/bash),生产环境可改为-s /sbin/nologin。

3.2.3COPY requirements.txt ./+pip install:分层缓存的生死线

这是 Docker 构建速度优化的最核心技巧。原因如下:

  • requirements.txt文件变更频率远低于源码(一周改一次依赖 vs 一天改十次代码);
  • pip install是构建中最耗时步骤(平均 2~5 分钟);
  • Docker 构建时,若某层缓存命中,则跳过该层及所有后续层。

如果写成:

COPY . . # ❌ 每次代码改一行,整个 COPY 层缓存失效 RUN pip install -r requirements.txt # ❌ 必须重新执行,哪怕 requirements.txt 没变

那么每次git commit后构建,都要重装所有 pip 包。而正确写法:

COPY requirements.txt ./ # ✅ 只有 requirements.txt 改了,才触发 pip install RUN pip install -r requirements.txt COPY . . # ✅ 代码变了,但 pip 层已缓存,秒级完成

实测数据:某项目requirements.txt有 42 个包,错误写法平均构建 4.8 分钟,正确写法首次 4.8 分钟,后续仅 12 秒。

3.2.4USER appuser:权限控制的“最后一道门”

这条指令必须放在COPY . .之后、CMD之前,因为:

  • COPY操作默认以 root 权限执行,这样才能把文件写入/app(root 拥有所有权);
  • USER appuser切换后,后续所有指令(包括CMD启动的进程)都以appuser身份运行;
  • 如果提前写USER appuser,则COPY会因权限不足失败(appuser对/app无写权限)。

提示:USER指令不会改变已存在文件的所有权。所以COPY进来的文件仍属 root,但appuser可读(因/app目录权限是 755)。若需appuser拥有文件,可在COPY后加RUN chown -R appuser:appuser /app,但通常没必要。

3.2.5CMD ["gunicorn", ...]:为什么不用shell形式?

两种写法:

CMD gunicorn --bind 0.0.0.0:8000 --workers 2 app:app # ❌ Shell 形式 CMD ["gunicorn", "--bind", "0.0.0.0:8000", "--workers", "2", "app:app"] # ✅ Exec 形式

区别在于进程树:

  • Shell 形式:/bin/sh -c "gunicorn ..."→gunicorn(子进程)
  • Exec 形式:gunicorn(PID=1,直接成为容器主进程)

为什么必须用 Exec 形式?

  • 容器的生命周期由 PID=1 进程决定。Shell 形式下,/bin/sh是 PID=1,gunicorn是子进程;若gunicorn崩溃,sh还活着,容器不退出,变成“僵尸容器”;
  • docker stop发送 SIGTERM 给 PID=1。Exec 形式下,信号直达gunicorn,可优雅关闭;Shell 形式下,sh收到信号但不转发给子进程,gunicorn强制 kill;
  • docker logs只能捕获 PID=1 进程的标准输出。Exec 形式下,日志干净;Shell 形式下,日志混杂sh的元信息。

4. 构建、运行、验证全流程:从命令行到可交付成果

4.1 构建镜像:不只是docker build

进入项目根目录,执行:

docker build -t my-flask-app:v1.0.0 .

参数详解:

  • -t my-flask-app:v1.0.0:打标签,格式仓库名:版本号。my-flask-app是镜像名,v1.0.0是语义化版本;
  • .:构建上下文(build context),即Dockerfile所在目录。Docker daemon 会将此目录下所有文件打包发送给构建引擎,因此务必确保.dockerignore文件存在。

.dockerignore内容应为:

__pycache__/ *.pyc *.pyo *.pyd .Python env/ venv/ .venv/ pip-log.txt .git .gitignore README.md

作用:避免无用文件(如.git)被发送到构建上下文,减小传输体积,加速构建。

构建过程输出解读(关键行):

[+] Building 12.3s (10/10) FINISHED => [internal] load build definition from Dockerfile 0.0s => => transferring dockerfile: 376B 0.0s => [internal] load .dockerignore 0.0s => => transferring context: 34B 0.0s => [internal] load metadata for docker.io/library/python:3.9-slim 0.5s => [1/6] FROM docker.io/library/python:3.9-slim@sha256:... 0.0s => [2/6] RUN groupadd -g 1001 -f appuser && useradd -s /bin/bash ... 1.2s => [3/6] WORKDIR /app 0.0s => [4/6] COPY requirements.txt ./ 0.0s => [5/6] RUN pip install --no-cache-dir -r requirements.txt 8.7s ← 最耗时层 => [6/6] COPY . . 0.1s => exporting to image 0.2s => => exporting layers 0.2s => => writing image sha256:1a2b3c... 0.0s => => naming to docker.io/library/my-flask-app:v1.0.0 0.0s

注意[5/6] RUN pip install耗时 8.7s,而[6/6] COPY . .仅 0.1s,印证了分层缓存的价值。

4.2 运行容器:不只是docker run

docker run -d \ --name my-flask-app-prod \ -p 8000:8000 \ -e SECRET_KEY="prod-secret-2024" \ -v $(pwd)/logs:/app/logs \ --restart unless-stopped \ my-flask-app:v1.0.0

参数逐条解析:

参数作用为什么必须
-d后台运行(detached)容器作为服务,不能占用终端
--name指定容器名便于后续docker logs my-flask-app-prod查日志,而非记一串 ID
-p 8000:8000端口映射(宿主机:容器)让外部能访问容器内服务;注意EXPOSE 8000只是声明,不自动映射
-e SECRET_KEY=...注入环境变量config.py中os.getenv('SECRET_KEY')读取此值,实现配置与代码分离
-v $(pwd)/logs:/app/logs挂载日志目录容器内/app/logs写入的日志,实时同步到宿主机当前目录,避免容器删除后日志丢失
--restart unless-stopped重启策略容器意外退出(如 OOM)时自动重启;unless-stopped表示docker stop后不自动重启,符合运维习惯

提示:生产环境建议用--restart on-failure:5(失败 5 次后停止),避免无限重启掩盖真正问题。

4.3 验证与调试:五步定位法

容器启动后,不要急着 curl,先按顺序执行以下五步:

  1. 检查容器状态

    docker ps -a | grep my-flask-app # 正常应显示 STATUS 为 Up X seconds,而非 Exited (1)
  2. 查看实时日志

    docker logs -f my-flask-app-prod # 应看到 gunicorn 启动日志,如 "Booting worker with pid: 1" # 若报错 "ModuleNotFoundError: No module named 'app'",说明 COPY 路径错了
  3. 进入容器内部诊断

    docker exec -it my-flask-app-prod /bin/bash # 检查文件是否存在 ls -l /app/ # 检查 Python 环境 which python && python -c "import flask; print(flask.__version__)" # 检查进程 ps aux | grep gunicorn
  4. 测试端口连通性

    # 在宿主机执行 curl -v http://localhost:8000 # 应返回 HTTP 200 和 "Hello from <容器ID>!" # 若超时,检查 -p 参数是否写错,或容器内 gunicorn 是否监听 0.0.0.0(而非 127.0.0.1)
  5. 验证环境变量注入

    docker exec my-flask-app-prod env | grep SECRET_KEY # 应输出 SECRET_KEY=prod-secret-2024 # 若为空,检查 -e 参数拼写,或 Dockerfile 中是否误用了 ENV(ENV 是构建时设,-e 是运行时覆盖)

常见陷阱:curl http://localhost:8000在宿主机成功,但在另一台机器失败?因为-p 8000:8000默认绑定127.0.0.1,需改为-p 0.0.0.0:8000:8000或在gunicorn中指定--bind 0.0.0.0:8000(我们已在 CMD 中指定,所以无需改)。

4.4 镜像瘦身实战:从 125MB 到 98MB

构建后执行docker images,发现my-flask-app:v1.0.0大小为 125MB。如何进一步压缩?三步法:

  1. 清理 pip 缓存
    在RUN pip install后加清理命令:

    RUN pip install --no-cache-dir -r requirements.txt && \ rm -rf /root/.cache/pip
  2. 使用多阶段构建(Multi-stage Build)
    将构建环境与运行环境分离:

    # 构建阶段:用完整镜像编译依赖 FROM python:3.9 AS builder WORKDIR /app COPY requirements.txt . RUN pip wheel --no-cache-dir --no-deps --wheel-dir /app/wheels -r requirements.txt # 运行阶段:用 slim 镜像,只复制 wheel 包 FROM python:3.9-slim RUN groupadd -g 1001 -f appuser && useradd -s /bin/bash -u 1001 -m appuser WORKDIR /app COPY --from=builder /app/wheels /wheels COPY --from=builder /app/requirements.txt . RUN pip install --no-cache-dir --find-links /wheels --no-index -r requirements.txt && \ rm -rf /wheels /root/.cache/pip COPY . . USER appuser EXPOSE 8000 CMD ["gunicorn", "--bind", "0.0.0.0:8000", "--workers", "2", "app:app"]
  3. 启用 BuildKit 加速
    在构建前启用:

    export DOCKER_BUILDKIT=1 docker build -t my-flask-app:v1.1.0 .

    BuildKit 会并行化构建步骤、智能缓存,实测多阶段构建时间从 12s 降至 7s,镜像体积从 125MB 降至 98MB。


5. 生产就绪 Checklist:那些文档里不写,但上线前必须做的 12 件事

5.1 镜像安全扫描(必须做)

即使你信任python:3.9-slim,也要扫描自己构建的镜像:

# 安装 Trivy(开源漏洞扫描器) brew install aquasecurity/trivy/trivy # Mac # 或 sudo apt-get install -y wget && sudo wget -O /usr/local/bin/trivy https://github.com/aquasecurity/trivy/releases/download/v0.45.0/trivy_0.45.0_Linux-64bit.tar.gz # 扫描镜像 trivy image my-flask-app:v1.0.0

重点关注:

  • CRITICAL/HIGH级别漏洞(如 OpenSSL CVE);
  • 基础镜像过期(如python:3.9-slim基于 Debian 11,但已 EOL);
  • 不必要的包(如curl、wget出现在生产镜像中)。

实操心得:将trivy集成到 CI 流程中,trivy image --severity CRITICAL,HIGH my-app,任一高危漏洞则构建失败。

5.2 日志与监控:别让容器变成黑盒

Docker 默认将容器 stdout/stderr 重定向到 JSON 文件(/var/lib/docker/containers/<id>/<id>-json.log),但这不够:

  • 日志文件无轮转,磁盘会被打爆;
  • 多容器日志混在一起,无法按服务过滤;
  • 缺少结构化字段(如 trace_id、user_id)。

生产级方案:

  1. 应用内使用结构化日志库(如 Python 的structlog);
  2. 容器启动时配置日志驱动:
    docker run --log-driver=json-file \ --log-opt max-size=10m \ --log-opt max-file=3 \ ...
  3. 用docker logs --since 24h按时间过滤,而非tail -f。

5.3 资源限制:防止单个容器吃光整台服务器

永远不要裸跑容器!必须设置内存和 CPU 限制:

docker run \ --memory=512m \ --memory-swap=512m \ --cpus=1.0 \ ...
  • --memory=512m:容器最多使用 512MB 内存,超限则被 OOM Killer 杀死;
  • --memory-swap=512m:禁用 swap,避免内存抖动;
  • --cpus=1.0:最多使用 1 个 CPU 核心(100% 时间片)。

注意:Kubernetes 中对应resources.limits.memory/cpu,必须与 Docker 命令一致。

5.4 网络与健康检查:让编排系统“看得见”你的服务

添加健康检查(Healthcheck):

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ CMD curl -f http://localhost:8000/health || exit 1

并在app.py中添加路由:

@app.route('/health') def health(): return {"status": "ok", "timestamp": time.time()}

作用:

  • Docker 会定期执行curl,若连续 3 次失败,标记容器为unhealthy;
  • Kubernetes 会据此触发重启或流量剔除;
  • --start-period=5s避免应用启动慢导致误判。

5.5 最终 Checklist 表格(上线前逐项打钩)

项目检查方法不通过后果是否完成
镜像体积 ≤ 120MBdocker images | grep my-flask-app镜像拉取慢,CI 存储压力大☐
无 root 进程docker exec my-app ps aux | grep "^root"安全策略拒绝部署☐
环境变量可注入docker run -e TEST=1 my-app env | grep TEST配置无法动态化☐
日志输出到 stdoutdocker logs my-app | head -5(应有 gunicorn 启动日志)日志收集器无法采集☐
健康检查端点可用docker exec my-app curl -f http://localhost:8000/health编排系统无法判断服务状态☐
端口绑定 0.0.0.0docker exec my-app netstat -tuln | grep :8000(应显示0.0.0.0:8000)外部无法访问☐
无调试工具残留docker exec my-app which bash vim curl(应全无输出)攻击面扩大☐
基础镜像非 latestdocker history my-app | head -5(首行应为python:3.9-slim)构建不可重现☐
.dockerignore 存在ls -la | grep .dockerignore构建上下文过大☐
多阶段构建启用docker build --progress=plain . | grep "FROM.*AS"镜像含构建工具,体积膨胀☐
Trivy 扫描无 CRITICAL 漏洞trivy image --severity CRITICAL my-app(应无输出)安全审计不通过☐
资源限制已配置docker inspect my-app | grep -A 5 Memory(应有 memory 字段)容器可能拖垮宿主机☐

提示:将此表做成自动化脚本,每次构建后自动执行,通过才允许推送到镜像仓库。


6. 常见问题与排查技巧实录

相关新闻

lingbot-map建图项目实战:从零跑通SLAM与地图构建

lingbot-map建图项目实战:从零跑通SLAM与地图构建

1. 从“lingbot-map”这个名字说起&#xff1a;它到底想解决什么问题第一次看到“lingbot-map”这个项目名&#xff0c;我脑子里冒出来的第一个念头是&#xff1a;这大概率是一个跟“地图构建”或者“空间映射”相关的机器人项目。拆开来看&#xff0c;“ling”可能是某个前缀缩…

2026/10/21 7:08:51 阅读更多 →
信息残缺项目拆解:从三个字母rea到完整技术画像

信息残缺项目拆解:从三个字母rea到完整技术画像

1. 当标题只剩下三个字母&#xff1a;一次极限信息压缩的解读实验拿到“rea”这个标题的时候&#xff0c;我第一反应是愣了一下。三个字母&#xff0c;没有正文&#xff0c;没有关键词&#xff0c;没有摘要&#xff0c;连一个标点符号都没有。这大概是我做内容拆解这些年遇到过…

2026/10/21 7:08:51 阅读更多 →
knowledge-work-plugins:插件化知识工作台架构设计与实操

knowledge-work-plugins:插件化知识工作台架构设计与实操

1. 从"知识工作插件"这个命名说起&#xff1a;它到底在解决什么问题第一次看到knowledge-work-plugins这个命名&#xff0c;我的直觉是&#xff1a;这不是一个普通的工具库&#xff0c;而是一套面向"知识工作者"的扩展机制。所谓知识工作者&#xff0c;说白…

2026/10/21 7:08:51 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年&#xff0c;我大部分时间都在跟浏览器标签页打交道&#xff0c;直到第一次把桌面应用跑起来&#xff0c;才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词&#xff0c;最开始是在某段时间频繁出现在同事的讨论里&#xff0c;一个用HTML…

2026/10/22 10:18:38 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介&#xff1a;亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档&#xff0c;主要面向智能锁安装人员、终端用户及售后维护者&#xff0c;帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件&#xff0c;大小4.37MB&#xff0c;内容结构清晰&#xff…

2026/10/21 9:32:40 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统&#xff0c;整车要求空载跑到8m/s&#xff0c;电机转速已经拉到额定点&#xff0c;驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令&#xff0c;转速纹丝不动&#xff0c;三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/10/22 9:44:47 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年&#xff0c;用它写过嵌入式驱动、做过游戏服务端&#xff0c;也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词&#xff0c;我其实挺感慨的——这门语言的难点从来不是语法多&#xff0c;而是知识点之间的耦合太深…

2026/10/21 9:32:40 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介&#xff1a;亚太天能T109滑盖智能锁产品说明书以PDF格式发布&#xff0c;适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开&#xff0c;先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能&#xff0c;再给出完整安装步骤&…

2026/10/21 9:32:40 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历&#xff1a;翻开源码注释、论文摘要或者一份专业报告&#xff0c;每个字都认识&#xff0c;连在一起却像天书。想找人聊聊&#xff0c;身边要么没人懂&#xff0c;要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型&#xff0c;几秒钟之后&a…

2026/10/21 9:31:32 阅读更多 →

日新闻

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型&#xff08;GMM&#xff09;的语音识别系统&#xff0c;很多人第一反应就是一堆数学公式&#xff0c;觉得门槛很高。但如果你真的在MATLAB里把它落地跑通&#xff0c;会发现核心其实就是三件事&#xff1a;把声音变成特征向量&#xff0c;用GMM给每个类别…

2026/10/22 1:47:45 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人&#xff0c;但落到具体操作上&#xff0c;无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程&#xff1a;对图像做反白、改调色板、彩色转灰度&#xff0c;最后把像素数据导出成txt文件。单看每一…

2026/10/21 21:32:28 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词&#xff0c;你会发现一个很有意思的现象&#xff1a;每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型&#xff0c;有人指的是运动控制系统&#xff0c;还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/22 5:12:39 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/21 21:50:56 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/21 21:51:55 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/21 16:24:48 阅读更多 →