
关注「Raymond运维」公众号,并设为「星标」,也可以扫描底部二维码加入群聊,第一时间获取最新内容,不再错过精彩内容。
一、概述
1.1 背景介绍
Docker 解决的核心问题就一个:"在我机器上能跑,到你那就不行了"。通过将应用及其依赖打包成镜像,保证开发、测试、生产环境完全一致。容器秒级启动(实测冷启动 0.5-2 秒),镜像分层复用节省存储和传输成本——一个 200MB 的基础镜像被 50 个应用共享,每个应用只需额外存储自己的差异层。
我们团队从 2019 年开始全面容器化,目前线上跑着 800+ 个容器,覆盖 Java、Go、Node.js、Python 四种技术栈。这篇文章把从安装到生产环境踩过的坑全部整理出来。
1.2 技术特点
环境一致性:镜像即环境,开发用的镜像和生产用的完全一样,彻底消除环境差异导致的问题 秒级启动:容器不需要启动完整操作系统,直接运行应用进程,启动速度比虚拟机快 10-100 倍 分层存储:镜像由多层只读层组成,相同的层在多个镜像间共享,节省磁盘空间和网络传输 资源隔离:基于 Linux cgroup 和 namespace 实现 CPU、内存、网络、文件系统隔离,互不干扰 声明式编排:通过 Dockerfile 和 docker-compose.yml 声明式定义应用环境,版本可控、可复现
1.3 适用场景
应用容器化部署:将传统应用打包成容器,统一部署和管理方式,降低运维复杂度 微服务架构:每个微服务独立容器化,独立部署、独立扩缩容,服务间通过网络通信 CI/CD 流水线:构建环境容器化,保证每次构建环境一致;测试环境秒级创建和销毁 开发测试环境标准化:新人入职 docker compose up一条命令拉起整套开发环境,不用花半天装依赖
1.4 环境要求
二、详细步骤
2.1 准备工作
2.1.1 系统检查
# 检查系统版本
cat /etc/os-release
# 检查内核版本(建议 5.x+)
uname -r
# 检查是否支持 overlay2(生产环境必须用 overlay2)
cat /proc/filesystems | grep overlay
# 检查可用磁盘空间(Docker 镜像和容器数据会占用大量空间)
df -h
# 检查是否已安装旧版本 Docker
docker --version 2>/dev/null && echo"Docker 已安装" || echo"Docker 未安装"
2.1.2 卸载旧版本
旧版本的包名可能是 docker、docker.io、docker-engine,必须先卸载干净:
# Ubuntu/Debian
sudo apt remove -y docker docker-engine docker.io containerd runc 2>/dev/null
sudo apt autoremove -y
# CentOS/Rocky Linux
sudo yum remove -y docker docker-client docker-client-latest \
docker-common docker-latest docker-latest-logrotate \
docker-logrotate docker-engine 2>/dev/null
注意:卸载旧版本不会删除 /var/lib/docker/ 下的镜像、容器和卷数据。如果要全新安装,手动删除:
# 这个操作会删除所有镜像和容器数据,执行前确认不需要保留
sudo rm -rf /var/lib/docker
sudo rm -rf /var/lib/containerd
2.2 安装 Docker CE
2.2.1 Ubuntu/Debian 安装
# 安装必要工具
sudo apt update
sudo apt install -y ca-certificates curl gnupg lsb-release
# 添加 Docker 官方 GPG 密钥
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
# 添加 Docker 官方源
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# 安装 Docker CE + CLI + containerd + Compose 插件 + BuildKit
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io \
docker-buildx-plugin docker-compose-plugin
# 验证安装
docker --version
docker compose version
2.2.2 CentOS/Rocky Linux 安装
# 安装 yum-utils(提供 yum-config-manager)
sudo yum install -y yum-utils
# 添加 Docker 官方源
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
# 安装
sudo yum install -y docker-ce docker-ce-cli containerd.io \
docker-buildx-plugin docker-compose-plugin
# 启动并设置开机自启
sudo systemctl start docker
sudo systemctl enable docker
# 验证
docker --version
docker compose version
2.2.3 配置非 root 用户运行 Docker
生产环境不建议用 root 跑 Docker 命令,把用户加到 docker 组即可:
# 创建 docker 组(安装时通常已自动创建)
sudo groupadd docker 2>/dev/null
# 将当前用户加入 docker 组
sudo usermod -aG docker $USER
# 重新登录使组权限生效(或执行以下命令临时生效)
newgrp docker
# 验证非 root 用户可以运行 docker
docker run hello-world
注意:docker 组的用户等同于拥有 root 权限(可以挂载宿主机任意目录),只把可信用户加入该组。
2.3 daemon.json 生产环境配置
这是我们线上所有 Docker 主机统一使用的 daemon.json,每个参数都有明确的理由:
{
"storage-driver": "overlay2",
"log-driver": "json-file",
"log-opts": {
"max-size": "100m",
"max-file": "3"
},
"registry-mirrors": [
"https://mirror.ccs.tencentyun.com",
"https://docker.mirrors.ustc.edu.cn"
],
"insecure-registries": [
"harbor.internal.example.com:5000"
],
"live-restore": true,
"default-ulimits": {
"nofile": {
"Name": "nofile",
"Hard": 65535,
"Soft": 65535
}
},
"bip": "172.17.0.1/16",
"default-address-pools": [
{
"base": "172.18.0.0/16",
"size": 24
}
],
"max-concurrent-downloads": 10,
"max-concurrent-uploads": 5,
"features": {
"buildkit": true
}
}
参数说明:
storage-driver: overlay2:生产环境唯一推荐的存储驱动,性能和稳定性最好。旧版本的 aufs/devicemapper 已废弃log-driver: json-file+max-size/max-file:限制每个容器日志最大 100MB,保留 3 个文件。这个必须配,否则日志会撑爆磁盘。线上出过好几次磁盘 100% 的事故,都是没限制日志大小导致的registry-mirrors:镜像加速器,国内拉 Docker Hub 镜像很慢,配了加速器后拉取速度从几十 KB/s 提升到几 MB/sinsecure-registries:内网 Harbor 私有仓库如果没配 HTTPS,必须加到这里,否则 push/pull 会报证书错误live-restore: true:Docker daemon 重启时不杀容器。升级 Docker 版本或重启 dockerd 时,正在运行的容器不受影响。生产环境必开bip:自定义 docker0 网桥的网段。默认 172.17.0.0/16 可能和公司内网冲突,提前规划好default-address-pools:自定义容器网络的地址池,避免docker network create时分配到已被占用的网段max-concurrent-downloads: 10:并行拉取镜像层数,默认 3 太少,设为 10 加快拉取速度
# 写入配置后重启 Docker
sudo mkdir -p /etc/docker
sudo vim /etc/docker/daemon.json # 写入上面的配置
sudo systemctl daemon-reload
sudo systemctl restart docker
# 验证配置生效
docker info | grep -E "Storage Driver|Logging Driver|Registry Mirrors|Live Restore"
2.4 Dockerfile 最佳实践
2.4.1 多阶段构建
多阶段构建是减小镜像体积最有效的手段。编译阶段用完整的 SDK 镜像,运行阶段只用最小的 runtime 镜像。
Go 应用示例(从 1.2GB 压缩到 15MB):
# 文件名:Dockerfile
# 阶段一:编译
FROM golang:1.22-alpine AS builder
WORKDIR /app
# 先复制 go.mod/go.sum,利用缓存(依赖不变时不重新下载)
COPY go.mod go.sum ./
RUN go mod download
# 再复制源码
COPY . .
# 编译为静态二进制(CGO_ENABLED=0 不依赖 C 库)
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /app/server ./cmd/server
# 阶段二:运行
FROM alpine:3.19
# 安装必要的 CA 证书(HTTPS 请求需要)和时区数据
RUN apk --no-cache add ca-certificates tzdata
# 非 root 用户运行
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
WORKDIR /app
COPY --from=builder /app/server .
COPY --from=builder /app/configs ./configs
# 设置时区
ENV TZ=Asia/Shanghai
USER appuser
EXPOSE8080
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget -qO- http://localhost:8080/health || exit 1
ENTRYPOINT ["./server"]
实测结果:
不用多阶段构建:golang:1.22 基础镜像 1.2GB + 应用 = 约 1.3GB 多阶段构建用 alpine:15MB
Java Spring Boot 应用示例:
# 文件名:Dockerfile
# 阶段一:构建
FROM maven:3.9-eclipse-temurin-21 AS builder
WORKDIR /app
COPY pom.xml .
# 先下载依赖(利用缓存层)
RUN mvn dependency:go-offline -B
COPY src ./src
RUN mvn package -DskipTests -B
# 解压 Spring Boot fat jar(分层优化)
RUN java -Djarmode=layertools -jar target/*.jar extract --destination /extracted
# 阶段二:运行
FROM eclipse-temurin:21-jre-alpine
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
WORKDIR /app
# 按变化频率从低到高复制(充分利用缓存)
COPY --from=builder /extracted/dependencies/ ./
COPY --from=builder /extracted/spring-boot-loader/ ./
COPY --from=builder /extracted/snapshot-dependencies/ ./
COPY --from=builder /extracted/application/ ./
ENV TZ=Asia/Shanghai
ENV JAVA_OPTS="-Xms512m -Xmx512m -XX:+UseG1GC"
USER appuser
EXPOSE8080
HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \
CMD wget -qO- http://localhost:8080/actuator/health || exit 1
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS org.springframework.boot.loader.launch.JarLauncher"]
实测结果:
不用多阶段构建:maven 基础镜像 800MB + 应用 = 约 900MB 多阶段构建用 JRE alpine:约 180MB
2.4.2 .dockerignore 文件
构建上下文会把当前目录所有文件发送给 Docker daemon,不配 .dockerignore 会把 node_modules、.git 等无关文件也发过去,构建慢且镜像臃肿。
# 文件名:.dockerignore
.git
.gitignore
.dockerignore
Dockerfile
docker-compose*.yml
README.md
LICENSE
docs/
# Node.js
node_modules
npm-debug.log
# Go
vendor/
# Java
target/
*.jar
*.class
# Python
__pycache__
*.pyc
.venv
venv
# IDE
.idea
.vscode
*.swp
*.swo
# 环境变量文件(绝对不能打进镜像)
.env
.env.*
这个文件必须有,特别是 .env 文件绝对不能打进镜像,里面有数据库密码等敏感信息。
2.4.3 Dockerfile 编写要点
COPY vs ADD:优先用 COPY,语义明确。ADD 会自动解压 tar 包和支持 URL 下载,容易产生意外行为 RUN 合并减少层数:每条 RUN 指令生成一层,合并可以减小镜像体积 # 错误写法:3 层,且第一层的 apt cache 无法被清理掉
RUN apt update
RUN apt install -y curl wget
RUN rm -rf /var/lib/apt/lists/*
# 正确写法:1 层,安装完立即清理缓存
RUN apt update && apt install -y --no-install-recommends curl wget \
&& rm -rf /var/lib/apt/lists/*非 root USER:容器默认以 root 运行,安全风险大。生产环境必须创建专用用户 HEALTHCHECK:定义健康检查,Docker 和编排工具(Compose/Swarm/K8s)据此判断容器是否正常 ARG vs ENV:ARG 只在构建阶段可用(如版本号),ENV 在运行时也可用(如配置参数)
2.5 Docker Compose 编排
2.5.1 完整的多服务编排示例
以下是一个典型的 Web 应用编排:Nginx 反向代理 + Node.js API + MySQL + Redis,包含健康检查、启动顺序控制、资源限制。
# 文件名:docker-compose.yml
services:
# Nginx 反向代理
nginx:
image:nginx:1.24-alpine
container_name:app-nginx
ports:
-"80:80"
-"443:443"
volumes:
-./nginx/conf.d:/etc/nginx/conf.d:ro
-./nginx/ssl:/etc/nginx/ssl:ro
-./frontend/dist:/var/www/html:ro
-nginx-logs:/var/log/nginx
depends_on:
api:
condition:service_healthy
restart:unless-stopped
deploy:
resources:
limits:
cpus:'1.0'
memory:256M
networks:
-frontend
# Node.js API 服务
api:
build:
context:./api
dockerfile:Dockerfile
container_name:app-api
env_file:
-.env
environment:
-NODE_ENV=production
-DB_HOST=mysql
-DB_PORT=3306
-REDIS_HOST=redis
-REDIS_PORT=6379
depends_on:
mysql:
condition:service_healthy
redis:
condition:service_healthy
healthcheck:
test:["CMD","wget","-qO-","http://localhost:3000/health"]
interval:15s
timeout:5s
retries:3
start_period:10s
restart:unless-stopped
deploy:
resources:
limits:
cpus:'2.0'
memory:1G
reservations:
cpus:'0.5'
memory:256M
networks:
-frontend
-backend
# MySQL 数据库
mysql:
image:mysql:8.0
container_name:app-mysql
environment:
MYSQL_ROOT_PASSWORD:${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE:${MYSQL_DATABASE}
MYSQL_USER:${MYSQL_USER}
MYSQL_PASSWORD:${MYSQL_PASSWORD}
volumes:
-mysql-data:/var/lib/mysql
-./mysql/conf.d:/etc/mysql/conf.d:ro
-./mysql/init:/docker-entrypoint-initdb.d:ro
healthcheck:
test:["CMD","mysqladmin","ping","-h","localhost","-u","root","-p${MYSQL_ROOT_PASSWORD}"]
interval:10s
timeout:5s
retries:5
start_period:30s
restart:unless-stopped
deploy:
resources:
limits:
cpus:'2.0'
memory:2G
command:>
--character-set-server=utf8mb4
--collation-server=utf8mb4_unicode_ci
--max-connections=500
--innodb-buffer-pool-size=1G
networks:
-backend
# Redis 缓存
redis:
image:redis:7-alpine
container_name:app-redis
command:>
redis-server
--requirepass ${REDIS_PASSWORD}
--maxmemory 512mb
--maxmemory-policy allkeys-lru
--appendonly yes
volumes:
-redis-data:/data
healthcheck:
test:["CMD","redis-cli","-a","${REDIS_PASSWORD}","ping"]
interval:10s
timeout:3s
retries:3
restart:unless-stopped
deploy:
resources:
limits:
cpus:'1.0'
memory:768M
networks:
-backend
networks:
frontend:
driver:bridge
backend:
driver:bridge
internal:true# 后端网络不暴露到外部
volumes:
mysql-data:
redis-data:
nginx-logs:
配套 .env 文件:
# 文件名:.env(不要提交到 Git)
MYSQL_ROOT_PASSWORD=your_root_password_here
MYSQL_DATABASE=app_production
MYSQL_USER=app_user
MYSQL_PASSWORD=your_app_password_here
REDIS_PASSWORD=your_redis_password_here
常用操作命令:
# 启动所有服务(后台运行)
docker compose up -d
# 查看服务状态
docker compose ps
# 查看某个服务的日志
docker compose logs -f api
# 重新构建并启动
docker compose up -d --build
# 停止所有服务(保留数据卷)
docker compose down
# 停止并删除数据卷(慎用,会丢数据)
docker compose down -v
2.6 网络模式
2.6.1 网络模式对比
2.6.2 自定义网络
生产环境不要用默认的 bridge 网络,自定义网络有 DNS 解析功能(容器间可以用容器名互相访问):
# 创建自定义网络(指定网段,避免和内网冲突)
docker network create \
--driver bridge \
--subnet 172.20.0.0/24 \
--gateway 172.20.0.1 \
app-network
# 查看网络详情
docker network inspect app-network
# 将已运行的容器接入网络
docker network connect app-network my-container
# 断开网络
docker network disconnect app-network my-container
注意:默认 bridge 网络(docker0)不支持容器名 DNS 解析,只有自定义网络才支持。这是很多人踩的坑——在默认网络里用容器名访问另一个容器,永远连不上。
2.7 存储卷
2.7.1 三种挂载方式对比
-v mydata:/data | |||
-v /host/path:/container/path | |||
--tmpfs /tmp |
2.7.2 Named Volume 数据持久化
# 创建命名卷
docker volume create mysql-data
# 查看卷详情
docker volume inspect mysql-data
# 使用命名卷启动 MySQL
docker run -d \
--name mysql \
-v mysql-data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=secret \
mysql:8.0
# 备份卷数据
docker run --rm \
-v mysql-data:/source:ro \
-v $(pwd):/backup \
alpine tar czf /backup/mysql-data-backup.tar.gz -C /source .
# 恢复卷数据
docker run --rm \
-v mysql-data:/target \
-v $(pwd):/backup \
alpine tar xzf /backup/mysql-data-backup.tar.gz -C /target
注意:docker compose down -v 会删除所有命名卷,数据库数据会丢失。生产环境执行前务必确认,或者养成习惯只用 docker compose down(不加 -v)。
三、示例代码和配置
3.1 完整配置示例
3.1.1 daemon.json 生产环境完整配置(带注释版)
// 文件路径:/etc/docker/daemon.json
// 注意:实际 JSON 文件不支持注释,这里仅作说明
{
// 存储驱动,生产环境只用 overlay2
"storage-driver": "overlay2",
// 日志驱动和限制(防止日志撑爆磁盘)
"log-driver": "json-file",
"log-opts": {
"max-size": "100m",
"max-file": "3"
},
// 镜像加速器(按需配置)
"registry-mirrors": [
"https://mirror.ccs.tencentyun.com"
],
// 内网私有仓库(HTTP 协议)
"insecure-registries": [
"harbor.internal.example.com:5000"
],
// Docker daemon 重启时保持容器运行
"live-restore": true,
// 容器默认 ulimit
"default-ulimits": {
"nofile": { "Name": "nofile", "Hard": 65535, "Soft": 65535 },
"nproc": { "Name": "nproc", "Hard": 65535, "Soft": 65535 }
},
// 自定义 docker0 网桥网段
"bip": "172.17.0.1/16",
// 自定义容器网络地址池
"default-address-pools": [
{ "base": "172.18.0.0/16", "size": 24 }
],
// 并行下载/上传层数
"max-concurrent-downloads": 10,
"max-concurrent-uploads": 5,
// 启用 BuildKit(构建速度更快,支持缓存挂载)
"features": { "buildkit": true },
// 数据根目录(默认 /var/lib/docker,磁盘不够时可改到大盘)
"data-root": "/var/lib/docker",
// DNS 配置(容器内 DNS 解析)
"dns": ["8.8.8.8", "114.114.114.114"]
}
3.1.2 Makefile 封装常用 Docker 操作
团队协作时,用 Makefile 封装常用命令,新人不用记一堆 docker 参数:
# 文件名:Makefile
APP_NAME := myapp
VERSION := $(shell git describe --tags --always --dirty 2>/dev/null || echo "dev")
REGISTRY := harbor.internal.example.com:5000
IMAGE := $(REGISTRY)/$(APP_NAME):$(VERSION)
.PHONY: build push run stop logs clean
# 构建镜像
build:
docker build -t $(IMAGE) -t $(REGISTRY)/$(APP_NAME):latest .
# 推送到私有仓库
push: build
docker push $(IMAGE)
docker push $(REGISTRY)/$(APP_NAME):latest
# 本地运行
run:
docker compose up -d
# 停止服务
stop:
docker compose down
# 查看日志
logs:
docker compose logs -f --tail=100
# 清理悬空镜像和停止的容器
clean:
docker system prune -f
docker image prune -f
# 进入容器 shell
shell:
docker compose exec api sh
# 数据库备份
db-backup:
docker compose exec mysql mysqldump -u root -p$(MYSQL_ROOT_PASSWORD)$(MYSQL_DATABASE) > backup_$(shell date +%Y%m%d_%H%M%S).sql
# 查看资源使用
stats:
docker stats --no-stream
3.2 实际应用案例
案例一:Node.js 应用容器化部署
场景描述:将一个 Express.js API 服务容器化,配合 PM2 进程管理,实现生产级部署。
Dockerfile:
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production && npm cache clean --force
FROM node:20-alpine
RUN apk --no-cache add tini
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY . .
ENV NODE_ENV=production
USER appuser
EXPOSE3000
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
CMD wget -qO- http://localhost:3000/health || exit 1
# 用 tini 作为 PID 1(正确处理信号,避免僵尸进程)
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["node", "src/index.js"]
运行结果:
$ docker build -t myapi:1.0 .
$ docker images myapi
REPOSITORY TAG IMAGE ID SIZE
myapi 1.0 a1b2c3d4e5f6 95MB
# 对比不用多阶段构建:约 350MB
案例二:零停机滚动更新
场景描述:生产环境更新应用版本,不中断服务。通过 docker compose 配合 Nginx upstream 实现。
实现步骤:
构建新版本镜像并推送到仓库 拉取新镜像,启动新容器 健康检查通过后,切换 Nginx 流量到新容器 停止旧容器
#!/bin/bash
# 文件名:rolling-update.sh
# 零停机滚动更新脚本
set -e
APP_NAME="app-api"
NEW_IMAGE="$1"
if [ -z "$NEW_IMAGE" ]; then
echo"用法: $0 <new_image:tag>"
exit 1
fi
echo"[1/5] 拉取新镜像: $NEW_IMAGE"
docker pull "$NEW_IMAGE"
echo"[2/5] 启动新容器"
docker compose up -d --no-deps --scale api=2 api
echo"[3/5] 等待新容器健康检查通过..."
sleep 10
NEW_CONTAINER=$(docker compose ps -q api | tail -1)
for i in $(seq 1 30); do
STATUS=$(docker inspect --format='{{.State.Health.Status}}'"$NEW_CONTAINER" 2>/dev/null)
if [ "$STATUS" = "healthy" ]; then
echo"新容器健康检查通过"
break
fi
echo"等待中... ($i/30)"
sleep 2
done
echo"[4/5] 缩容,移除旧容器"
docker compose up -d --no-deps --scale api=1 api
echo"[5/5] 清理旧镜像"
docker image prune -f
echo"更新完成!"
四、最佳实践和注意事项
4.1 最佳实践
4.1.1 镜像瘦身
选择合适的基础镜像:
基础镜像 大小 适用场景 ubuntu:22.04 77MB 需要完整包管理器的场景 debian:bookworm-slim 74MB 需要 glibc 但不需要完整 Debian alpine:3.19 7MB Go/Rust 等静态编译语言,体积最小 distroless 2-20MB 安全要求最高的场景,无 shell 无包管理器 scratch 0MB 纯静态二进制,如 Go 应用 我们团队的做法:Go 应用用 alpine,Java 应用用 eclipse-temurin JRE alpine,Node.js 用 node alpine。
用 dive 分析镜像层:
# 安装 dive
wget https://github.com/wagoodman/dive/releases/download/v0.12.0/dive_0.12.0_linux_amd64.tar.gz
tar -zxvf dive_0.12.0_linux_amd64.tar.gz
sudo mv dive /usr/local/bin/
# 分析镜像每一层的内容和大小
dive myapp:latestdive 会显示每一层新增了哪些文件、占了多少空间,能快速定位镜像臃肿的原因。实测用 dive 分析后,我们把一个 Java 镜像从 600MB 优化到 180MB。
4.1.2 安全加固
非 root 运行:容器默认以 root 运行,一旦容器逃逸攻击者就是宿主机 root。Dockerfile 中必须创建专用用户:
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser只读文件系统:容器运行时文件系统设为只读,防止恶意写入:
docker run --read-only --tmpfs /tmp --tmpfs /var/run myapp:latest需要写入的目录用 tmpfs 或 volume 挂载。
Drop 不需要的 Linux Capabilities:
# 移除所有 capabilities,只保留需要的
docker run --cap-drop ALL --cap-add NET_BIND_SERVICE myapp:latest默认容器有 14 个 capabilities,大部分应用只需要 1-2 个。全部 drop 再按需 add 是最安全的做法。
镜像安全扫描:
# 用 Trivy 扫描镜像漏洞(推荐,速度快、数据库全)
trivy image myapp:latest
# 只显示高危和严重漏洞
trivy image --severity HIGH,CRITICAL myapp:latest
# 集成到 CI/CD:发现高危漏洞时构建失败
trivy image --exit-code 1 --severity HIGH,CRITICAL myapp:latest我们团队在 CI 流水线中强制扫描,HIGH 以上漏洞必须修复才能上线。
4.1.3 日志管理
json-file 驱动限制大小(daemon.json 中已配置):
{
"log-driver": "json-file",
"log-opts": {
"max-size": "100m",
"max-file": "3"
}
}每个容器最多 3 个日志文件,每个 100MB,总共 300MB 上限。不配这个,跑几天日志就能把磁盘撑爆。
单个容器覆盖全局日志配置:
# docker-compose.yml 中
services:
api:
logging:
driver:json-file
options:
max-size:"200m"
max-file:"5"集中日志收集方案:
# 用 fluentd 驱动将日志发送到集中式日志平台
services:
api:
logging:
driver:fluentd
options:
fluentd-address:"10.0.0.50:24224"
tag:"docker.{{.Name}}"
fluentd-async:"true"
4.1.4 资源限制
容器不限制资源的话,一个容器内存泄漏就能把整台宿主机拖垮。生产环境必须设资源限制。
# 命令行方式限制资源
docker run -d \
--name myapp \
--memory 1g \
--memory-swap 1g \
--cpus 2.0 \
--pids-limit 200 \
--oom-score-adj 500 \
myapp:latest
参数说明:
--memory 1g:内存上限 1GB,超过会触发 OOM Killer--memory-swap 1g:设为和 memory 相同值表示禁用 swap(生产环境建议禁用,swap 会导致性能急剧下降)--cpus 2.0:最多使用 2 个 CPU 核心--pids-limit 200:限制容器内最大进程数,防止 fork 炸弹--oom-score-adj 500:OOM 优先级,值越大越容易被 kill(保护宿主机上更重要的进程)
4.1.5 CI/CD 集成
BuildKit 缓存加速构建:
# 启用 BuildKit(daemon.json 中已配置,也可以通过环境变量临时启用)
export DOCKER_BUILDKIT=1
# 使用缓存挂载加速依赖下载(Go 示例)
# Dockerfile 中:
# RUN --mount=type=cache,target=/go/pkg/mod go mod download
# RUN --mount=type=cache,target=/root/.cache/go-build go build -o /app/server
# 使用内联缓存(CI/CD 跨构建复用缓存)
docker build \
--build-arg BUILDKIT_INLINE_CACHE=1 \
--cache-from harbor.example.com/myapp:latest \
-t harbor.example.com/myapp:v1.2.3 \
.多平台构建(buildx):
# 创建多平台构建器
docker buildx create --name multiarch --use
# 同时构建 amd64 和 arm64 镜像并推送
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t harbor.example.com/myapp:v1.2.3 \
--push \
.ARM 服务器(如 AWS Graviton)越来越多,提前做好多平台支持能省不少麻烦。
Harbor 私有仓库:
# 登录私有仓库
docker login harbor.internal.example.com:5000
# 打标签并推送
docker tag myapp:v1.2.3 harbor.internal.example.com:5000/project/myapp:v1.2.3
docker push harbor.internal.example.com:5000/project/myapp:v1.2.3
4.2 注意事项
4.2.1 配置注意事项
daemon.json 改错了会导致 Docker 无法启动,改之前先备份。
修改 daemon.json 后必须 systemctl daemon-reload && systemctl restart dockerlive-restore: true时重启 dockerd 不会杀容器,但升级大版本(如 24.x 到 27.x)时可能不兼容,建议先在测试环境验证bip网段规划要提前和网络团队确认,避免和公司内网、VPN 网段冲突。我们踩过的坑:默认 172.17.0.0/16 和办公网 VPN 冲突,导致开发机连不上容器
4.2.2 常见错误
systemctl start docker | ||
docker system prune -a | ||
ss -tlnp | grep <port> | ||
4.2.3 兼容性问题
cgroup v1 vs v2:Ubuntu 22.04 默认用 cgroup v2,部分老版本应用(如 Java 8u191 以下)不识别 cgroup v2 的内存限制,会导致 JVM 看到的是宿主机全部内存而非容器限制。解决办法:升级 JDK 版本或在 GRUB 中加 systemd.unified_cgroup_hierarchy=0回退到 v1Alpine 与 glibc:Alpine 用 musl libc 而非 glibc,部分依赖 glibc 的应用(如某些 Python C 扩展、Oracle JDK)在 Alpine 上会报错。解决办法:换用 debian-slim 基础镜像,或安装 gcompat 兼容层 Docker Compose V1 vs V2:V1( docker-compose,Python 实现)已停止维护,V2(docker compose,Go 实现)是 Docker CLI 插件。两者配置文件基本兼容,但部分边缘行为有差异。新项目直接用 V2
五、故障排查和监控
5.1 故障排查
5.1.1 日志查看
# 查看容器日志(最近 100 行,实时跟踪)
docker logs --tail 100 -f <container_name>
# 查看指定时间段的日志
docker logs --since "2026-02-07T00:00:00" --until "2026-02-07T12:00:00" <container_name>
# 查看 Docker daemon 日志
sudo journalctl -u docker -f
# 查看容器内进程
docker top <container_name>
# 查看容器详细信息(启动参数、网络、挂载等)
docker inspect <container_name>
# 查看容器事件流(启动、停止、OOM 等事件)
docker events --since "1h"
5.1.2 常见问题排查
问题一:容器启动后立即退出
# 查看退出码
docker inspect --format='{{.State.ExitCode}}' <container_name>
# 常见退出码含义:
# 0 - 正常退出(CMD/ENTRYPOINT 执行完毕)
# 1 - 应用错误
# 137 - 被 SIGKILL 杀死(通常是 OOM)
# 139 - 段错误(Segmentation Fault)
# 143 - 被 SIGTERM 正常终止
# 查看最后的日志
docker logs --tail 50 <container_name>
解决方案:
退出码 0:CMD 命令执行完就退出了,需要一个前台进程保持运行。常见错误是用了 CMD service nginx start(后台启动后进程退出),应该用CMD ["nginx", "-g", "daemon off;"]退出码 137:容器被 OOM Killer 杀了,增大内存限制或优化应用内存使用 退出码 1:应用报错,看日志找具体原因
问题二:容器网络不通
# 检查容器网络配置
docker inspect --format='{{json .NetworkSettings.Networks}}' <container_name> | jq
# 进入容器排查
docker exec -it <container_name> sh
# 容器内检查 DNS
cat /etc/resolv.conf
nslookup mysql
# 容器内检查连通性
ping <target_ip>
wget -qO- http://<target>:<port>/health
# 宿主机检查 iptables 规则(Docker 通过 iptables 实现端口映射和网络隔离)
sudo iptables -t nat -L -n | grep <port>
sudo iptables -L DOCKER -n
解决方案:
容器间互通但用容器名访问不了:检查是否在同一个自定义网络中,默认 bridge 网络不支持 DNS 容器访问外网不通:检查宿主机的 IP 转发 sysctl net.ipv4.ip_forward,值必须为 1端口映射不生效:检查宿主机防火墙规则,Docker 的 iptables 规则可能被其他防火墙工具覆盖
问题三:存储空间耗尽
# 查看 Docker 磁盘使用情况
docker system df
# 详细查看每个镜像/容器/卷的占用
docker system df -v
# 查看 overlay2 存储占用
du -sh /var/lib/docker/overlay2/* | sort -rh | head -20
解决方案:
# 清理已停止的容器、未使用的网络、悬空镜像和构建缓存
docker system prune -f
# 更激进的清理:同时删除未被任何容器使用的镜像
docker system prune -a -f
# 只清理超过 24 小时的资源
docker system prune -a -f --filter "until=24h"
# 清理未使用的卷(数据库卷要小心)
docker volume prune -f
预防措施:定期清理 + 日志大小限制 + 监控磁盘使用率告警
问题四:容器内存 OOM
# 查看容器资源使用情况
docker stats --no-stream <container_name>
# 查看容器内存限制
docker inspect --format='{{.HostConfig.Memory}}' <container_name>
# 查看是否发生过 OOM
docker inspect --format='{{.State.OOMKilled}}' <container_name>
# 查看系统 OOM 日志
dmesg | grep -i "oom\|killed"
解决方案:
确认内存限制是否合理: docker stats观察实际内存使用峰值,限制值设为峰值的 1.5 倍Java 应用特别注意:JVM 堆内存(-Xmx)+ 非堆内存(Metaspace、线程栈、NIO Buffer)总和不能超过容器内存限制。经验公式:容器内存 = Xmx * 1.5 禁用 swap: --memory-swap设为和--memory相同值
5.2 性能监控
5.2.1 docker stats 实时监控
# 查看所有容器资源使用(实时刷新)
docker stats
# 查看指定容器(不刷新,输出一次)
docker stats --no-stream app-api app-mysql app-redis
# 输出示例:
# CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
# a1b2c3d4e5f6 app-api 2.35% 256MiB / 1GiB 25.00% 1.2GB / 800MB 50MB / 10MB 35
# b2c3d4e5f6a1 app-mysql 5.12% 1.1GiB / 2GiB 55.00% 500MB / 1.5GB 2GB / 500MB 42
# c3d4e5f6a1b2 app-redis 0.50% 50MiB / 768MiB 6.51% 200MB / 300MB 1MB / 5MB 5
5.2.2 cAdvisor + Prometheus + Grafana 监控方案
生产环境推荐用 cAdvisor 采集容器指标,Prometheus 存储,Grafana 展示。
# 文件名:docker-compose.monitoring.yml
services:
cadvisor:
image:gcr.io/cadvisor/cadvisor:v0.49.1
container_name:cadvisor
ports:
-"8080:8080"
volumes:
-/:/rootfs:ro
-/var/run:/var/run:ro
-/sys:/sys:ro
-/var/lib/docker/:/var/lib/docker:ro
-/dev/disk/:/dev/disk:ro
privileged:true
restart:unless-stopped
prometheus:
image:prom/prometheus:v2.51.0
container_name:prometheus
ports:
-"9090:9090"
volumes:
-./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
-prometheus-data:/prometheus
command:
-'--config.file=/etc/prometheus/prometheus.yml'
-'--storage.tsdb.retention.time=30d'
restart:unless-stopped
grafana:
image:grafana/grafana:10.4.0
container_name:grafana
ports:
-"3000:3000"
volumes:
-grafana-data:/var/lib/grafana
environment:
-GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD:-admin}
restart:unless-stopped
volumes:
prometheus-data:
grafana-data:
Prometheus 采集配置:
# 文件路径:prometheus/prometheus.yml
global:
scrape_interval:15s
scrape_configs:
-job_name:'cadvisor'
static_configs:
-targets:['cadvisor:8080']
5.2.3 监控指标说明
5.3 备份与恢复
5.3.1 备份策略
#!/bin/bash
# 文件名:docker-backup.sh
# Docker 数据备份脚本
BACKUP_DIR="/data/backups/docker"
DATE=$(date +%Y%m%d_%H%M%S)
RETENTION_DAYS=7
mkdir -p "$BACKUP_DIR"
echo"=== Docker 数据备份开始: $DATE ==="
# 备份所有命名卷
for vol in $(docker volume ls -q); do
echo"备份卷: $vol"
docker run --rm \
-v "$vol":/source:ro \
-v "$BACKUP_DIR":/backup \
alpine tar czf "/backup/${vol}_${DATE}.tar.gz" -C /source .
done
# 备份 docker-compose 配置
echo"备份 Compose 配置..."
tar czf "$BACKUP_DIR/compose_configs_${DATE}.tar.gz" \
/opt/apps/*/docker-compose.yml \
/opt/apps/*/.env 2>/dev/null
# 清理过期备份
find "$BACKUP_DIR" -name "*.tar.gz" -mtime +$RETENTION_DAYS -delete
echo"=== 备份完成 ==="
5.3.2 恢复流程
停止服务: docker compose down恢复卷数据: docker run --rm \
-v mysql-data:/target \
-v /data/backups/docker:/backup \
alpine tar xzf /backup/mysql-data_20260207_030000.tar.gz -C /target验证完整性: docker compose up -d && docker compose ps检查数据:连接数据库确认数据完整
六、总结
6.1 技术要点回顾
安装与配置:daemon.json 是 Docker 生产环境的核心配置,日志大小限制、live-restore、网段规划这三项必须配置,否则迟早出事故 Dockerfile 编写:多阶段构建是镜像瘦身的关键手段,Go 应用从 1.3GB 压缩到 15MB,Java 应用从 900MB 压缩到 180MB。.dockerignore 必须有,防止敏感文件打进镜像 Compose 编排:healthcheck + depends_on condition 控制启动顺序,deploy.resources 限制资源,networks 隔离前后端流量 安全加固:非 root 运行、只读文件系统、drop capabilities、Trivy 镜像扫描,四道防线缺一不可 监控与运维:docker stats 日常巡检,cAdvisor + Prometheus + Grafana 长期监控,定期 docker system prune 清理空间
6.2 进阶学习方向
Kubernetes 容器编排:Docker 解决了单机容器化问题,K8s 解决多机编排问题。掌握 Docker 后学 K8s 是自然的进阶路径
学习资源:Kubernetes 官方文档 实践建议:先用 kind 或 minikube 搭建本地集群练手,再上生产 镜像构建优化:深入 BuildKit 缓存机制、多平台构建、镜像签名和供应链安全
学习资源:BuildKit 官方文档 实践建议:在 CI/CD 中实践 BuildKit 缓存挂载,构建速度能提升 50% 以上 容器运行时:了解 containerd、CRI-O 等底层运行时,理解 Docker 只是容器生态的上层工具
6.3 参考资料
Docker 官方文档 - 最权威的 Docker 使用参考 Dockerfile 最佳实践 - 官方推荐的 Dockerfile 编写规范 Docker Compose 规范 - Compose 文件格式完整参考 Docker 安全基准 - CIS Docker Benchmark,安全加固检查清单 Awesome Docker - Docker 生态工具和资源汇总
附录
A. Docker 命令速查表
# 镜像操作
docker images # 列出本地镜像
docker pull <image>:<tag> # 拉取镜像
docker build -t <name>:<tag> . # 构建镜像
docker push <image>:<tag> # 推送镜像
docker rmi <image> # 删除镜像
docker image prune -f # 清理悬空镜像
docker save -o backup.tar <image> # 导出镜像为 tar
docker load -i backup.tar # 从 tar 导入镜像
docker tag <source> <target> # 给镜像打标签
docker history <image> # 查看镜像构建历史
# 容器操作
docker run -d --name <name> <image> # 后台运行容器
docker ps # 查看运行中的容器
docker ps -a # 查看所有容器(含已停止)
docker stop <container> # 停止容器
docker start <container> # 启动已停止的容器
docker restart <container> # 重启容器
docker rm <container> # 删除容器
docker rm -f <container> # 强制删除运行中的容器
docker exec -it <container> sh # 进入容器 shell
docker logs --tail 100 -f <container> # 查看容器日志
docker inspect <container> # 查看容器详细信息
docker cp <container>:/path /host/path # 从容器复制文件到宿主机
docker stats # 查看容器资源使用
docker top <container> # 查看微
信
群
WeChat group
为了方便大家更好的交流运维等相关技术问题,创建了微信交流群,需要加群的小伙伴们可以扫一扫下面的二维码加我为好友拉您进群(备注:加群)。

代
码
仓
库
| 代码仓库 | 网址 |
| Github | https://github.com/raymond999999 |
| Gitee | https://gitee.com/raymond9 |
博
客
Blog
| 博客 | 网址 |
| https://blog.csdn.net/qq_25599925 | |
| 稀土掘金 | https://juejin.cn/user/4262187909781751 |
| 知识星球 | https://wx.zsxq.com/group/15555885545422 |
| 阿里云社区 | https://developer.aliyun.com/profile/snzh3xpxaf6sg |
| 腾讯云社区 | https://cloud.tencent.com/developer/user/11823619 |
| 华为云社区 | https://developer.huaweicloud.com/usercenter/mycommunity/dynamics |
访问博客网站,查看更多优质原创内容。
夜雨聆风