1. 为什么“本地构建 + 远程运行”不是偷懒,而是工程闭环的刚需
你有没有遇到过这样的场景:在自己笔记本上写完一个 Python Web 服务,用docker build -t myapp .顺利打包,docker run -p 8080:8080 myapp也能跑通——但一到云服务器上,就卡在pull access denied、no matching manifest、exec format error,甚至ModuleNotFoundError: No module named 'flask'?别急着重装 Docker 或怀疑镜像损坏。这根本不是环境问题,而是整个构建-分发-执行链路里,最常被跳过的那根关键筋膜:本地与远程之间,镜像不是“能跑就行”,而是必须“可验证、可追溯、可复现、可审计”。
我带过好几个跨团队协作项目,发现一个惊人共性:90% 的线上部署失败,根源不在 Kubernetes 配置或云厂商网络策略,而在于开发机上那个看似完美的Dockerfile,压根没经过“真实远程环境”的压力测试。本地构建时用的是 macOS 的 Apple Silicon 芯片,基础镜像是python:3.11-slim(默认是 amd64 架构),推到云服务器(通常是 x86_64)后,Docker 引擎会尝试自动拉取对应架构镜像,但一旦基础层不匹配,整个构建缓存就全废;更隐蔽的是,本地pip install依赖时用了.whl文件里的编译扩展(比如numpy的 OpenBLAS 绑定),而云服务器缺少对应系统库,镜像一运行就 segmentation fault——这种问题,docker run在本地永远暴露不出来。
所以,“本地构建运行 docker,或把本地镜像发给云服务器运行”,表面看是两个动作,实则是一体两面:本地构建是可控的沙盒,远程运行为的是真实战场验证。它解决的不是“能不能跑”,而是“跑得稳不稳、快不快、安不安全、改不改得动”。比如某次为某高校实验室部署图像标注平台,我们坚持所有镜像必须先在模拟云环境的虚拟机里完成三轮压力测试(CPU 占用峰值、内存泄漏监测、日志落盘一致性),才允许推送到生产集群。结果上线后三个月零重启,而隔壁团队用“本地 build 后直接 push 到云 registry 再 run”的方式,两周内因glibc版本冲突导致服务间歇性超时,排查了整整三天。
关键词里虽未明写,但这个动作天然绑定三个硬核要素:镜像可移植性(portability)、构建确定性(reproducibility)、分发可靠性(reliability)。它们不是 Docker 的附加功能,而是容器化交付的底层契约。接下来,我会从最朴素的实践出发,不讲概念,只拆解每一步“为什么必须这样操作”、每一步“踩过什么坑”、每一步“怎么一眼看出对错”。
2. 本地构建:从Dockerfile到可运行镜像的七道关卡
很多人以为docker build就是“把代码塞进容器”,其实它是一场精密的编译流水线。一次成功的本地构建,至少要闯过七道关卡。漏掉任何一道,后续所有远程操作都是空中楼阁。
2.1 第一关:基础镜像选型——不是越小越好,而是“够用且稳定”
FROM python:3.11-slim看似合理,但它暗藏两个陷阱:
- slim 镜像剔除了
apt、curl、bash等调试工具,一旦容器内服务崩溃,你连ps aux都执行不了; - slim 镜像的
glibc版本往往比主流云服务器(如 Ubuntu 22.04 LTS)低,导致某些 C 扩展模块(如psycopg2-binary)在远程运行时报symbol lookup error。
我试过用python:3.11-bullseye(基于 Debian 11)替代slim,虽然镜像体积大了 40MB,但远程部署成功率从 73% 提升到 99.2%。为什么?因为 Bullseye 的glibc版本(2.31)与绝大多数云服务器发行版兼容,且保留了完整的包管理能力。判断依据很简单:登录你的目标云服务器,执行ldd --version,记下glibc版本号,再查 Docker Hub 上基础镜像的发行版说明——这才是选型的黄金法则,而不是看镜像大小排行榜。
提示:永远优先选择
*-bullseye或*-jammy(Ubuntu 22.04)后缀的基础镜像,它们生命周期长、补丁更新及时、社区支持充分。避免使用latest标签,必须锁定具体版本,如python:3.11.9-bullseye。
2.2 第二关:多阶段构建——不是为了炫技,而是斩断“构建污染”
假设你的项目需要编译前端资源(React/Vue),Dockerfile里写了RUN npm install && npm run build。如果不用多阶段构建,最终镜像里会残留node_modules/、package-lock.json、甚至npm本身——这些对运行时毫无价值,却让镜像体积膨胀 3 倍,还引入额外攻击面(比如npm漏洞)。
正确做法是:
# 构建阶段 FROM node:18-bullseye AS builder WORKDIR /app COPY package*.json ./ RUN npm ci --only=production COPY . . RUN npm run build # 运行阶段 FROM python:3.11.9-bullseye WORKDIR /app COPY --from=builder /app/dist ./static COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . CMD ["gunicorn", "--bind", "0.0.0.0:8000", "app:app"]这里的关键是--from=builder。它确保只有dist/目录被复制到最终镜像,node_modules/、src/、webpack.config.js全部被隔离在构建阶段,永不进入运行时。实测下来,某图像处理 API 的镜像体积从 1.2GB 降到 380MB,启动时间缩短 40%,且docker scan安全扫描高危漏洞数归零。
2.3 第三关:依赖安装——pip install的三个致命误区
误区一:用
pip install -r requirements.txt直接安装requirements.txt通常由pip freeze > requirements.txt生成,包含所有依赖及其子依赖的精确版本。但子依赖可能有冲突(比如requests依赖urllib3<2.0,而botocore依赖urllib3>=1.26.0,<1.27.0),pip默认会降级安装,导致运行时行为异常。
✅ 正确做法:用pip-tools生成锁文件。先写requirements.in(只列顶层依赖),再运行pip-compile requirements.in生成requirements.txt,它会解析所有传递依赖并锁定唯一解。误区二:忽略
--no-cache-dir
Docker 构建时,pip默认使用/root/.cache/pip缓存。但该目录在镜像层中不会被清理,导致镜像体积虚高,且不同构建机器的缓存内容不一致,破坏构建确定性。
✅ 必须加--no-cache-dir参数。误区三:不指定
--find-links和--trusted-host
如果你用私有 PyPI 源(如 Nexus),pip install会因证书或域名验证失败而中断。
✅ 在RUN指令中显式声明:RUN pip install --no-cache-dir --find-links https://pypi.example.com/simple/ --trusted-host pypi.example.com -r requirements.txt
2.4 第四关:工作目录与权限——WORKDIR不是摆设,chown不是可选
很多Dockerfile写WORKDIR /app后直接COPY . .,但没注意:COPY指令默认以 root 用户身份复制文件,导致/app下所有文件属主为root:root。如果应用进程以非 root 用户(如www-data)运行,就会因权限不足无法读取配置文件或写入日志。
标准解法是三步走:
# 1. 创建非 root 用户 RUN addgroup -g 1001 -f appgroup && adduser -S appuser -u 1001 # 2. 复制文件后修改属主 COPY --chown=appuser:appgroup . /app # 3. 切换用户 USER appuser:appgroup--chown参数是 Docker 17.09+ 引入的,它在复制时直接设置文件属主,比RUN chown -R appuser:appgroup /app更高效、更安全(避免中间状态)。某次为某公司部署风控模型服务,因忘记--chown,模型加载时提示Permission denied,排查了两小时才发现是/app/models/目录权限问题。
2.5 第五关:端口与健康检查——EXPOSE是文档,HEALTHCHECK是心跳
EXPOSE 8000只是告诉 Docker “这个容器监听 8000 端口”,它不开启端口、不映射端口、不保证端口可用。真正决定服务是否健康的,是HEALTHCHECK。
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ CMD curl -f http://localhost:8000/health || exit 1这段指令让 Docker 定期执行curl,连续 3 次失败就标记容器为 unhealthy。它迫使你在应用里实现/health接口(检查数据库连接、缓存可用性、磁盘空间),而不是只依赖进程存活。某次线上事故中,API 进程仍在运行,但 Redis 连接池已耗尽,HEALTHCHECK在 2 分钟内触发重启,而纯靠ps aux监控的旧方案,故障持续了 17 分钟。
2.6 第六关:构建参数与环境变量——ARG和ENV的分工铁律
ARG用于构建时传入的值(如BUILD_VERSION),仅在docker build阶段有效,不会出现在最终镜像中;ENV设置的变量会持久化到镜像层,所有后续RUN、CMD都能访问。
常见错误:把敏感信息(如 API Key)用ARG传入,再用ENV赋值,以为就安全了。错!ARG值会留在构建缓存中,docker history <image>一眼可见。
✅ 安全做法:敏感信息绝不进镜像。用--build-arg传入占位符,运行时通过-e或--env-file注入:
# 构建时不传敏感值 docker build --build-arg APP_ENV=prod -t myapp . # 运行时注入 docker run -e API_KEY=xxx -e DB_PASSWORD=yyy myapp2.7 第七关:标签与元数据——docker tag不是锦上添花,而是溯源基石
构建完镜像,别只用docker tag myapp:latest。必须打上语义化版本 + Git Commit ID + 构建时间三重标签:
# 获取当前 commit id COMMIT_ID=$(git rev-parse --short HEAD) # 获取构建时间(ISO8601) BUILD_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ") docker build -t myapp:${COMMIT_ID} \ -t myapp:2024.06.15 \ -t myapp:2024.06.15-${COMMIT_ID} \ --build-arg BUILD_TIME=${BUILD_TIME} \ .这样,当你在云服务器上看到myapp:2024.06.15-abc123这个镜像,就能立刻git checkout abc123还原代码,git show abc123查看变更,docker inspect查看构建时间。某次回滚事故中,运维同事靠docker images | grep myapp三秒定位到故障版本对应的 Git 提交,5 分钟内完成回退,而隔壁团队因镜像无标签,花了 40 分钟逐个docker run测试历史镜像。
3. 本地运行验证:不是docker run一下就完事,而是五维真机体检
本地docker run成功,只证明“容器能启动”,不等于“服务能交付”。真正的本地运行验证,必须覆盖五个维度:功能、性能、日志、网络、安全。少一个维度,远程就多一分风险。
3.1 功能验证:用curl+jq模拟真实调用链
别只访问http://localhost:8000/看返回 HTML。要模拟生产环境的真实调用:
- API 接口:用
curl -X POST -H "Content-Type: application/json" -d '{"text":"hello"}' http://localhost:8000/process | jq '.',检查 JSON 结构、HTTP 状态码、响应时间; - 静态资源:用
curl -I http://localhost:8000/static/main.js,验证Content-Type: application/javascript和Cache-Control: public, max-age=31536000是否正确; - 健康检查:
curl -f http://localhost:8000/health,确认返回{"status":"ok","db":"connected"}。
我习惯写一个test-local.sh脚本,把所有核心接口调用封装成函数,每次构建后自动执行:
#!/bin/bash set -e # 任一命令失败即退出 echo "Testing /health..." curl -f http://localhost:8000/health echo "Testing /api/v1/process..." curl -f -X POST -H "Content-Type: application/json" \ -d '{"input":"test"}' http://localhost:8000/api/v1/process | jq -e '.result' echo "Testing static file..." curl -f -I http://localhost:8000/static/logo.png | grep "200 OK"set -e是关键,它让脚本在任一测试失败时立即终止,避免“部分成功”的假象。
3.2 性能验证:用ab或wrk压测,不是看单次响应
curl只测单次延迟,而生产环境面对的是并发请求。必须用压测工具验证:
ab -n 1000 -c 100 http://localhost:8000/health(Apache Bench,简单易用);wrk -t12 -c400 -d30s http://localhost:8000/api/v1/process(更精准,支持自定义脚本)。
重点观察三项指标:
- 吞吐量(Requests/sec):是否达到预期(如 500 req/s);
- P95 延迟(ms):95% 的请求响应时间是否 < 200ms;
- 错误率(Failed requests):是否为 0。
某次为某电商后台构建搜索服务镜像,本地curl响应 120ms,但wrk -c200下 P95 延迟飙升到 1800ms,错误率 12%。排查发现是Dockerfile中ulimit -n 1024设置过低,容器内文件描述符耗尽。加到65536后,P95 降至 190ms,错误率为 0。
3.3 日志验证:docker logs要看“结构化”,不是“刷屏”
应用日志必须是 JSON 格式,且包含timestamp、level、service、trace_id字段。验证方法:
# 启动容器并实时跟踪日志 docker run -d --name test-app -p 8000:8000 myapp:latest docker logs -f test-app | jq -r 'select(.level=="error")'这条命令会过滤出所有level为error的日志,并格式化输出。如果日志是纯文本(如ERROR:root:Something went wrong),jq会报错,立刻暴露日志格式问题。某次上线前,我们就是靠这条命令,在本地发现了数据库连接池初始化失败的日志,而docker logs test-app | tail根本看不到——因为错误日志被淹没在千行 debug 日志里。
3.4 网络验证:docker network模拟多容器拓扑
生产环境 rarely 是单容器。必须验证容器间网络:
# 创建自定义网络 docker network create app-net # 启动数据库容器 docker run -d --name db --network app-net -e POSTGRES_PASSWORD=pass postgres:15 # 启动应用容器,连接 db docker run -d --name app --network app-net -p 8000:8000 \ -e DB_HOST=db -e DB_NAME=myapp myapp:latest然后进app容器,用ping db和nc -zv db 5432测试连通性。这比localhost测试更真实,因为localhost在容器内指向自身,而db指向 DNS 解析的容器 IP。某次微服务架构中,因Dockerfile里硬编码了DB_HOST=localhost,本地单容器运行正常,一上云就连接拒绝——docker network本地验证提前揪出了这个硬编码。
3.5 安全验证:docker scan是免费的渗透测试
Docker Desktop 自带docker scan,它基于 Snyk 数据库,能扫描镜像中的已知漏洞:
docker scan --accept-license myapp:latest重点关注Critical和High级别漏洞。例如,扫描显示openssl有 CVE-2023-0286(高危),解决方案不是手动升级openssl,而是换基础镜像:从python:3.11-bullseye升级到python:3.11-bookworm(Debian 12),因为 Bookworm 的openssl已修复该漏洞。docker scan不是“扫完就扔”,而是驱动你做架构升级的决策依据。
4. 镜像分发:从docker push到云服务器的七种路径与避坑指南
本地构建验证通过后,镜像如何抵达云服务器?这不是简单的docker push,而是一场关于网络、存储、权限、一致性的综合博弈。我梳理出七种主流路径,每种都附带血泪教训。
4.1 路径一:Docker Hub 公共仓库——适合学习,不适合生产
docker push username/myapp:tag最简单,但问题明显:
- 速率限制:免费账户每 6 小时最多拉取 200 次,企业级应用分分钟超限;
- 隐私风险:镜像公开,源码、配置、密钥若未清理,全网可见;
- 合规障碍:金融、医疗类项目严禁使用公共仓库。
✅ 替代方案:用 Docker Hub 的私有仓库(需付费),或迁移到自建仓库。
4.2 路径二:自建 Harbor 仓库——企业级首选,但部署成本高
Harbor 是 CNCF 毕业项目,提供镜像签名、漏洞扫描、权限控制。部署它需要:
- 一台独立服务器(4C8G 起步);
- 配置 HTTPS 证书(Let's Encrypt 或商业证书);
- 集成 LDAP/AD 认证。
某公司曾用 Harbor,但因证书过期未及时更新,导致所有 CI/CD 流水线docker login失败,停摆 8 小时。✅ 教训:Harbor 必须配置证书自动续期(certbot renew --deploy-hook "systemctl reload harbor"),且监控harbor-core容器健康状态。
4.3 路径三:云厂商托管仓库——省心但有绑定风险
AWS ECR、阿里云 ACR、腾讯云 TCR 都提供免运维的镜像仓库。优势是开箱即用、无缝集成自家 CI/CD。但隐患是:
- 网络延迟:如果你的云服务器在华北,而 ECR 在华东,
docker pull可能慢至 5 分钟; - 跨云迁移难:镜像元数据、权限模型、API 接口各不相同。
✅ 最佳实践:地域就近原则。云服务器在哪,仓库就建在哪。同时,用skopeo工具做跨仓库同步备份:
skopeo copy docker://us-west-2.ecr.amazonaws.com/myapp:latest \ docker://cn-north-1.acr.aliyuncs.com/myapp:latest4.4 路径四:docker save/load——离线环境终极方案
当云服务器完全无外网(如军工、电力内网),docker push/pull失效。此时用:
# 本地保存为 tar 包 docker save -o myapp-latest.tar myapp:latest # 用 U 盘或内网 FTP 传输到云服务器 # 云服务器加载 docker load -i myapp-latest.tar⚠️ 注意:docker save保存的是所有层,包括构建缓存层。如果Dockerfile有RUN apt update && apt install -y xxx,save包会包含整个apt缓存,体积暴增。✅ 优化:构建前加--squash(Docker 1.13+ 实验特性)或用docker export $(docker create myapp:latest)导出运行时文件系统(但丢失元数据)。
4.5 路径五:rsync同步 overlayfs——极客向,性能最优
Docker 镜像本质是overlay2文件系统。你可以直接同步/var/lib/docker/overlay2/下的 layer 目录:
# 本地获取镜像 layer id LAYER_ID=$(docker image inspect myapp:latest -f '{{.RootFS.Layers}}') # rsync 同步对应目录到云服务器 rsync -avz --delete /var/lib/docker/overlay2/${LAYER_ID}/ user@cloud-server:/var/lib/docker/overlay2/✅ 优势:秒级同步,无压缩解压开销。❌ 劣势:需 root 权限,且overlay2目录结构版本敏感(Docker 20.10 与 24.0 不兼容)。仅推荐给熟悉 Docker 存储驱动的资深运维。
4.6 路径六:CI/CD 流水线直推——自动化核心,但需防止单点故障
Jenkins/GitLab CI 中,构建完成后自动docker push到仓库。但必须设置:
- 重试机制:网络抖动时
push失败,自动重试 3 次; - 镜像签名:用
cosign对镜像签名,云服务器docker pull前校验签名; - 灰度发布:先推
myapp:staging,验证通过后再docker tag为myapp:latest。
某次因 Jenkins 服务器磁盘满,docker push失败但未告警,导致生产环境一直运行旧镜像。✅ 教训:所有 CI 步骤必须有on_failure通知(邮件/钉钉),且docker push后加docker pull验证。
4.7 路径七:buildx构建 +load——解决架构不匹配的银弹
前面提到的exec format error,根源是本地与远程 CPU 架构不同(ARM vs AMD64)。docker buildx是官方解决方案:
# 启用 buildx docker buildx install # 创建跨平台构建器 docker buildx create --name mybuilder --use # 构建多架构镜像 docker buildx build --platform linux/amd64,linux/arm64 \ -t myapp:multiarch --push . # 云服务器(AMD64)拉取时自动选对应架构 docker pull myapp:multiarchbuildx底层用 QEMU 模拟不同架构,--push会将多个架构的镜像层推送到同一仓库标签下。云服务器docker pull时,Docker 引擎根据uname -m自动选择匹配层。这是目前最可靠、最标准化的跨架构分发方案,无需手动qemu-user-static注册。
5. 云服务器运行:从docker run到生产就绪的十二项加固清单
镜像抵达云服务器,只是万里长征第一步。docker run命令背后,藏着十二项必须落实的加固措施。漏掉任何一项,都可能让服务在流量高峰时崩塌,或在安全审计中一票否决。
5.1 容器运行时:runc还是crun?选对引擎影响性能 30%
Docker 默认用runc,但crun(用 C 重写的 OCI 运行时)在启动速度和内存占用上更优:
# 安装 crun sudo apt-get install -y crun # 配置 Docker 使用 crun echo '{"default-runtime": "crun"}' | sudo tee /etc/docker/daemon.json sudo systemctl restart docker实测:某 Python 服务容器启动时间从runc的 1.2s 降至crun的 0.85s,内存占用减少 18%。但crun对内核版本有要求(≥5.4),老系统慎用。
5.2 资源限制:--memory和--cpus不是可选项,而是熔断开关
不设限制的容器,会吃光服务器所有内存,触发 OOM Killer 杀死其他进程。必须:
docker run -d \ --memory=1g \ --memory-swap=1g \ --cpus=1.5 \ --restart=always \ --name myapp \ myapp:latest--memory-swap=1g表示内存 + swap 总上限 1G,防止 swap 泛滥;--cpus=1.5限制 CPU 时间片,避免单容器霸占所有核心;--restart=always确保进程崩溃后自动重启。
某次因未设--memory,一个日志分析容器内存泄漏,吃光 16G 内存,导致 MySQL 被 OOM Kill,业务中断 22 分钟。
5.3 存储驱动:overlay2是标配,但--storage-opt能救命
Docker 默认overlay2驱动,但需优化:
// /etc/docker/daemon.json { "storage-driver": "overlay2", "storage-opts": [ "overlay2.override_kernel_check=true", "overlay2.mountopt=nodev" ] }nodev选项禁止在挂载点创建设备文件,提升安全性;override_kernel_check允许在较老内核上启用overlay2。某次在 CentOS 7.6(内核 3.10)上,因未加override_kernel_check,Docker 启动失败,折腾半天才发现是这个参数。
5.4 网络模式:--network=host是性能利器,也是安全雷区
--network=host让容器共享宿主机网络命名空间,省去iptablesNAT 开销,网络延迟降低 40%。但风险是:
- 容器内
netstat -tuln能看到宿主机所有端口; - 容器可直接操作宿主机
iptables规则。
✅ 适用场景:高性能网络代理、监控采集器等可信组件。
❌ 禁用场景:Web 应用、数据库等面向外部的服务。
替代方案:用--network=bridge+--publish显式暴露端口,安全可控。
5.5 日志驱动:json-file会撑爆磁盘,local是救星
Docker 默认json-file驱动,日志存为 JSON 文件,无限增长。必须改:
// /etc/docker/daemon.json { "log-driver": "local", "log-opts": { "max-size": "10m", "max-file": "3" } }local驱动用二进制格式存储,体积比 JSON 小 60%,且自动轮转。某次因未配置,/var/lib/docker/containers/目录日志占满 100G 磁盘,服务全部宕机。
5.6 安全配置:--read-only+--tmpfs锁死文件系统
让容器文件系统只读,杜绝恶意写入:
docker run -d \ --read-only \ --tmpfs /tmp:rw,size=100m \ --tmpfs /run:rw,size=50m \ --tmpfs /var/run:rw,size=50m \ myapp:latest--read-only挂载根文件系统为只读,--tmpfs为必须写入的目录(/tmp、/run)提供内存临时文件系统。某次安全审计中,此项配置帮我们通过了“容器不可篡改性”条款。
5.7 进程管理:--init是僵尸进程收割者
容器内 PID 1 进程(如python app.py)若不处理SIGTERM,子进程会变成僵尸。加--init启用轻量级 init 系统(tini):
docker run -d --init myapp:latesttini会转发信号、收割僵尸进程。某次因未加--init,一个长连接服务产生大量僵尸进程,ps aux | grep defunct显示 200+,最终fork()失败,新连接全部拒绝。
5.8 健康检查:--health-cmd必须与HEALTHCHECK一致
Docker CLI 的--health-cmd会覆盖Dockerfile中的HEALTHCHECK。必须保持一致:
docker run -d \ --health-cmd="curl -f http://localhost:8000/health || exit 1" \ --health-interval=30s \ --health-timeout=3s \ --health-start-period=30s \ --health-retries=3 \ myapp:latest否则docker ps显示healthy,但实际服务已挂。某次因--health-cmd路径写错(/healthz而非/health),健康检查永远成功,故障未被发现。
5.9 标签管理:--label是自动化运维的钥匙
给容器打标签,方便脚本识别:
docker run -d \ --label com.example.service=web \ --label com.example.env=prod \ --label com.example.version=2024.06.15 \ myapp:latest之后可用docker ps --filter "label=com.example.env=prod"精准筛选生产容器,或用docker inspect提取标签做自动化部署。某次用 Ansible 批量滚动更新,就靠--label区分服务类型,零误操作。
5.10 监控集成:--cgroup-parent让 cgroup 一目了然
Docker 默认把容器放在/docker/cgroup 下,但和宿主机进程混在一起。用--cgroup-parent归类:
sudo mkdir -p /cgroup/app docker run -d --cgroup-parent=/cgroup/app myapp:latest这样top、htop、systemd-cgtop能清晰看到app组的 CPU/内存消耗,便于容量规划。
5.11 时区同步:-v /etc/localtime:/etc/localtime:ro是时间一致性保障
容器内时区默认 UTC,但日志、定时任务需本地时区。挂载宿主机时区文件:
docker run -d -v /etc/localtime:/etc/localtime:ro myapp:latest否则crontab任务按 UTC 执行,比预期晚 8 小时。某次定时备份脚本因此漏跑,数据恢复花了 6 小时。
5.12 故障自愈:--restart=unless-stopped+--oom-score-adj=-500
最后两道保险:
--restart=unless-stopped:容器崩溃自动重启,除非手动docker stop;- `--oom-score-adj=-50