开发环境跑通容器只是第一步。生产环境要关心安全、资源、日志、更新和监控。
镜像安全#
用非 root 用户运行
# 错误的做法(容器内以 root 运行)
FROM node:20-alpine
COPY . /app
CMD ["node", "/app/server.js"]
# 容器内进程 = root,万一被攻破有更高权限
# 正确的做法
FROM node:20-alpine
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
COPY --chown=appuser:appgroup . /app
CMD ["node", "/app/server.js"]固定版本,不追 latest
# ❌ 坏:今天和明天拉到的可能是不同镜像
FROM node:latest
# ✅ 好:锁定具体版本
FROM node:20.11.1-alpine3.19
# ✅ 更好:锁定 digest(固定内容标识;仍需验证来源、支持周期和安全更新)
FROM node:<受支持版本>@sha256:<核验过的完整digest>Docker Scout 漏洞扫描
# 扫描镜像的已知漏洞
docker scout quickview myapp:latest
docker scout cves myapp:latest
# 查看策略合规状态
docker scout policy myapp:latest资源限制#
# Compose 方式(推荐)
services:
api:
image: myapp:latest
deploy:
resources:
limits:
memory: 512M # 硬上限
cpus: "1.0" # 最多 1 核
reservations:
memory: 256M # reservation 不等于物理内存的绝对保证,执行/调度语义按环境判断# 命令行方式
docker run -d \
--memory=512m \
--memory-swap=1g \
--cpus=1.5 \
--cpuset-cpus=0-2 \
--name api myapp:latest如果不设限制会怎样?
一个容器可能吃光所有内存,导致 OOM Killer 开始杀进程(包括宿主机进程)。生产环境必须设置上限。
日志管理#
配置日志驱动
一般 Linux Engine 可通过 daemon 配置文件表达以下设置;NixOS 用 virtualisation.docker.daemon.settings 声明,不手工覆盖受管理文件。默认设置主要作用于新建容器,既有容器需按计划重建:
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}不改这个,某个容器日志疯狂输出可能撑爆磁盘。
查看日志
docker logs --tail 100 myweb
docker logs --since 30m myweb
docker logs -f myweb # 实时跟踪集中日志收集
生产环境建议将日志发送到集中系统:
services:
api:
logging:
driver: syslog
options:
syslog-address: "tcp://logserver:514"健康检查与自愈#
内置健康检查
services:
api:
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 60s# 查看健康状态
docker ps --format "table {{.Names}}\t{{.Status}}"
# api Up 10 minutes (healthy)自动重启不健康容器
普通 Engine 的 restart policy 根据进程退出动作,不因 unhealthy 自动重启;Swarm 服务调度和第三方监控有不同机制。示例健康命令要求镜像内实际提供 curl:
# 方案 1:Docker Swarm(内置自愈)
docker stack deploy -c compose.yaml myapp
# 方案 2:第三方 autoheal 示例(将授予 rootful daemon 管理能力,先审查镜像及配置)
docker run -d --name autoheal \
-v /var/run/docker.sock:/var/run/docker.sock \
willfarrell/autohealUpdates:滚动更新与停机边界#
Rolling update(Swarm)
services:
api:
image: myapp:latest
deploy:
replicas: 3
update_config:
parallelism: 1 # 每次更新 1 个副本
delay: 10s # 间隔 10 秒
order: start-first # 先启新再停旧docker service update --image myapp:v2 myapp_apiCompose 单机更新
# 拉新镜像 + 重建容器
docker compose pull
docker compose up -d --remove-orphans
# 注意:单机 Compose 有短暂中断Secrets 管理#
# Swarm secrets:先安全生成并保护来源文件,勿在命令行硬编码凭据
docker secret create db_password /path/to/protected-password-file# swarm compose 文件
secrets:
db_password:
external: true
services:
db:
secrets:
- db_password
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_password# 非 Swarm 方式:用 .env
# .env(加入 .gitignore)
DB_PASSWORD=<从受控凭据来源提供>绝对不要:
- 把密码 hardcode 在 Dockerfile 里(
ENV DB_PASSWORD=123)——密码进了镜像层,docker history可见 - 把
.env提交到 git
备份与恢复#
卷备份
先停止写入或使用数据库原生一致性备份。恢复应在停服务、空的新卷或已备份目标上验证;tar 解压会覆盖同名文件,且不会自动清理目标多余文件。下面命令只演示文件归档,不保证数据库一致性。
# 备份
docker run --rm -v mydata:/data -v "$(pwd)":/backup alpine \
tar czf /backup/mydata-$(date +%Y%m%d).tar.gz -C /data .
# 恢复
docker run --rm -v mydata:/data -v "$(pwd)":/backup alpine \
tar xzf /backup/mydata-20260620.tar.gz -C /data定期清理#
# crontab 或 systemd timer
# 每天凌晨 3 点清理 72 小时前的未使用镜像
0 3 * * * docker image prune -af --filter "until=72h"安全检查清单#
- Docker daemon 是否暴露了 TCP socket?(检查 Unix socket 权限;TCP 使用认证和加密,Unix socket 也不是自动安全)
- 容器是不是都设了
--memory和--cpus限制? - 日志大小上限设了没?(
daemon.json的max-size) - 有没有容器在挂载
/var/run/docker.sock?(极度危险) -
.env是否在.gitignore里? - 有没有用
:latest标签? - 有没有跑
docker scout扫过镜像?
生产验收的补充边界
start-first 与副本数不保证零停机:应用就绪、连接排空、端口冲突、迁移和集群容量都要验证。非 root 用户、rootless、capabilities/seccomp、只读挂载和最小权限分别解决不同问题,挂载 Docker socket 会扩大控制权限,即使绑定挂载标成只读也不限制 API 操作。
普通 Compose 同样支持 file secrets;它保护注入方式,但不自动加密源文件。扫描结果有时效与覆盖范围,锁定旧 digest 也不会自动获得安全更新。自动 prune 需保留回滚镜像和维护窗口,镜像创建时间过滤不等于最后使用时间。
参考与延伸
根据技术笔记重新整理,示例使用通用名称。