Docker 容器技术从入门到精通完全指南
系统讲解 Docker 容器技术的核心知识点,涵盖 Dockerfile 编写、镜像构建、多阶段构建、容器运维等完整知识体系。
Docker 容器技术从入门到精通完全指南
本文系统梳理 Docker 容器技术的核心知识点,涵盖 Linux 内核三大机制、Dockerfile 编写、镜像构建优化、容器运维实践等完整知识体系。
概述
容器技术的本质是一种轻量级的虚拟化解决方案,它通过 Linux 内核的三大机制实现:
| 机制 | 核心作用 |
|---|---|
| Namespace | 隔离 — 让容器拥有独立的进程、网络、文件系统等视图 |
| CGroup | 限制 — 限定容器对 CPU、内存、磁盘 IO 等资源的使用 |
| UnionFS | 分层 — 通过写时复制实现镜像分层和容器轻量级创建 |
理解这三大机制的原理,是掌握容器技术的根基。
第一章:Linux 内核三大机制
1.1 Namespace 名称空间
名称空间提供了资源的视图隔离,让容器拥有"独立"某个资源的感觉,但实际上共享宿主机内核。Linux 提供以下 6 种名称空间:
| 名称空间 | 隔离内容 | 效果 |
|---|---|---|
| PID | 进程 ID | 容器内只能看到自己的进程,PID=1 的进程通常是容器主进程 |
| Network | 网络栈 | 容器拥有自己独立的网络设备、IP 地址、端口、路由表等 |
| Mount | 文件系统挂载点 | 容器拥有自己独立的文件系统视图,看不到宿主机的挂载点 |
| UTS | 主机名和域名 | 容器可以有自己的 hostname,独立于宿主机 |
| IPC | 进程间通信 | 隔离 System V IPC、POSIX 消息队列等,防止容器间通信干扰 |
| User | 用户和用户组 ID | 容器内的 root 用户(UID=0)在宿主机上只是普通用户,增强安全性 |
实现原理
从操作系统角度来说,一个容器本质就是一个进程。在 clone() 创建进程时,传入不同参数实现不同程度隔离:
CLONE_NEWPID:让进程内有独立的 PID 空间CLONE_NEWNET:让进程拥有独立的网络设备、路由表CLONE_NEWNS:让进程拥有独立的文件系统挂载点CLONE_NEWIPC:隔离进程间通信资源CLONE_NEWUTS:隔离主机名和域名CLONE_NEWUSER:隔离用户和用户组 ID
实践中的有趣现象
隔离了 PID 后,在容器内执行 ps -aux 应该看不到宿主机进程,但实际却能看到所有进程。原因在于 ps、top 等命令读取的是 /proc 文件系统,而 /proc 属于 Mount 名称空间。当只隔离 PID 而未隔离 Mount 时,父进程和子进程看到的文件是一样的。
只有当 Mount 也被隔离时,容器内才能真正做到"只见自己"的效果,仿佛真的创建了一个独立的系统。
1.2 CGroup(Control Group)
CGroup 实现资源的限制和核算,包括 CPU、内存、磁盘 IO、最大进程数等。
CPU 资源限制
CPU 的 CGroup 有几个关键参数:
- cpu.cfs_period_us:CPU 调度周期,默认 100ms
- cpu.cfs_quota_us:该控制组在一个调度周期内占用 CPU 的时间
例如 quota=50000,period=100000,则 50000/100000 = 0.5,表示该控制组最多使用 0.5 个 CPU 核心。
- cpu.shares:同一层级下多个控制组间的 CPU 分配比例
当 group1 的 shares=1024,group2 的 shares=4096 时,比值为 1:4。在 5 核机器上,如果两组都需要 5 个 CPU,则 group1 分配 1 个,group2 分配 4 个。
在 K8s 中:
- requests →
cpu.shares:初始申请量,实际使用可超发 - limits →
cpu.cfs_quota_us/cpu.cfs_period_us:资源使用上限
内存资源限制
- memory.limit_in_bytes:容器内所有进程可占用的物理内存上限
注意:如果父级 CGroup 设置了 500M,子 CGroup 最多只能设置到 500M。
-
memory.oom_control:OOM 控制开关
- 值为 0:开启 OOM Killer(默认)
- 值为 1:关闭 OOM Killer
-
memory.usage_in_bytes:只读参数,记录容器内所有进程占用的物理内存总量
1.3 UnionFS
UnionFS(联合文件系统)是 Docker 镜像分层和容器分层的基础,通过写时复制(Copy-on-Write)机制实现。
分层结构
- lowerdir:只读镜像层
- upperdir:可写层,容器运行时修改的内容
- merged:展示最终的文件视图,合并上下层内容
┌─────────────────────────────────┐
│ merged (展现层) │
├─────────────────────────────────┤
│ upperdir (容器层,可写) │
├─────────────────────────────────┤
│ lowerdir (镜像层,只读) │
└─────────────────────────────────┘写时复制原理
当容器需要修改某个文件时,先从只读层复制到可写层,然后修改。这样既保证了镜像层不变,又实现了容器间的隔离。
强调:容器内挂载的文件系统类型不是 ext4、xfs,而是 overlay 文件系统。
第二章:Dockerfile 编写完全指南
Dockerfile 是构建镜像的指令文件,每一行是一条指令,构建时从上到下依次执行。
2.1 基础指令一览
| 指令 | 作用 |
|---|---|
| FROM | 指定基础镜像 |
| LABEL | 添加元数据(作者、版本等) |
| WORKDIR | 设置工作目录 |
| COPY | 复制本地文件到容器 |
| ADD | 复制文件(支持 URL 和自动解压) |
| RUN | 执行命令 |
| ENV | 设置环境变量 |
| EXPOSE | 声明端口(文档作用) |
| USER | 切换用户 |
| VOLUME | 声明卷挂载点 |
| CMD | 容器启动命令(可被覆盖) |
| ENTRYPOINT | 容器启动命令(不可覆盖) |
2.2 FROM — 指定基础镜像
# 使用官方镜像
FROM python:3.9-alpine
FROM ubuntu:20.04
FROM node:20
# 使用第三方镜像
FROM redis:7-alpine
# 从零开始(适用于 Go 等静态编译语言)
FROM scratch最佳实践:
- 尽量用具体版本 tag,避免用
latest- 优先使用
alpine、slim等精简版本- 避免多层"二手镜像"(如
FROM ubuntu再装 Python,不如直接用FROM python:xxx)
2.3 RUN — 执行命令
# 单条命令
RUN apt-get update && apt-get install -y curl
# 多条命令合并(减少镜像层数)
RUN apt-get update && \
apt-get install -y curl vim git && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
# 安装 Python 依赖
RUN pip install --no-cache-dir -r requirements.txt
# 创建用户和用户组
RUN addgroup -S appgroup && adduser -S appuser -G appgroup最佳实践:多条 RUN 命令合并成一条,可以减少镜像层数,进而减小镜像体积。
2.4 COPY vs ADD — 复制文件
# COPY - 复制本地文件到容器(优先使用)
COPY requirements.txt /app/
COPY . /app/
# ADD - 类似 COPY,但支持 URL 和自动解压
ADD https://example.com/file.tar.gz /app/
ADD file.tar.gz /app/ # 自动解压到 /app/推荐用 COPY。ADD 功能过于强大,不够直观,且在某些场景下行为不易理解。
2.5 WORKDIR — 设置工作目录
WORKDIR /app
COPY . .
RUN npm install# 也可以设置不存在的目录,会自动创建
WORKDIR /app/subdir最佳实践:用 WORKDIR 代替
cd /path &&这样的绝对路径写法,更清晰、更可维护。
2.6 ENV — 设置环境变量
ENV APP_HOME /app
ENV NODE_ENV production
ENV PORT=8080# 运行时可覆盖
docker run -e NODE_ENV=development myimage2.7 EXPOSE — 声明端口
EXPOSE 8080
EXPOSE 80 443注意:这只是文档声明,实际端口映射靠
docker run -p参数。容器实际监听的端口由应用本身决定。
2.8 USER — 切换用户
# 创建用户和用户组
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
# 切换用户(之后的命令以此用户身份执行)
USER appuser安全规范:容器内应避免以 root 用户运行,使用非 root 用户可以减少安全风险。
2.9 VOLUME — 声明卷
VOLUME ["/data"]
VOLUME ["/data", "/logs"]声明容器内的目录为卷挂载点,运行时可通过 -v 覆盖。
第三章:CMD vs ENTRYPOINT 详解
这是 Dockerfile 中最容易混淆的两个指令。
3.1 核心区别
| 特性 | CMD | ENTRYPOINT |
|---|---|---|
| 能否被 docker run 覆盖 | 能 | 不能(命令本身不可覆盖) |
| 用途 | 提供默认参数 | 定义固定的可执行程序 |
| 典型场景 | 变参数、变命令 | 固定程序、固定工具 |
3.2 CMD — 可被覆盖
# 写法一:exec 格式(推荐)
CMD ["python", "app.py", "--port", "8080"]
# 写法二:shell 格式
CMD python app.py --port 8080# 启动,CMD 的命令被完全覆盖
docker run myimage python other.py --arg13.3 ENTRYPOINT — 不可被覆盖
ENTRYPOINT ["python", "app.py"]# 启动,追加的参数传给 app.py
docker run myimage --port 9000
# 实际执行: python app.py --port 90003.4 组合使用:ENTRYPOINT + CMD
这是最常见的用法:
ENTRYPOINT ["python", "app.py"]
CMD ["--help"]效果:
- 默认执行:
python app.py --help - 覆盖 CMD:
docker run myimage --version→python app.py --version
CMD 作为默认参数,可以被覆盖。
3.5 实际案例分析
案例 1:官方 Redis 镜像
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["redis-server"]# 默认启动 redis-server
docker run redis
# 实际执行: docker-entrypoint.sh redis-server
# 覆盖 CMD 执行 redis-cli
docker run redis redis-cli
# 实际执行: docker-entrypoint.sh redis-cli这里的 docker-entrypoint.sh 脚本会判断参数并做相应处理:
#!/bin/bash
set -e
if [ "$1" = 'redis-server' ]; then
# 处理 redis-server 特有逻辑
...
fi
exec "$@"案例 2:带默认参数的 Java 应用
ENTRYPOINT ["java", "-jar", "/app.jar"]
CMD ["--spring.profiles.active=prod"]# 默认用 prod 配置启动
docker run myjava
# 覆盖为 dev 配置
docker run myjava --spring.profiles.active=dev3.6 shell 格式 vs exec 格式
| 特性 | shell 格式 | exec 格式 |
|---|---|---|
| 信号处理 | 会丢失 SIGTERM | 正确传递 |
| 环境变量展开 | 会展开 | 不会展开 |
| PID=1 | 由 shell 担任 | 由命令本身担任 |
# 错误写法(CMD 作为 shell 的子进程,无法接收信号)
CMD python app.py
# 正确写法(app.py 是 PID=1)
CMD ["python", "-u", "app.py"]3.7 信号传递问题
当你执行 docker stop 时:
- exec 格式:SIGTERM 直接发给 app.py,可以优雅关闭
- shell 格式:SIGTERM 发给 shell,shell 再转发,容易超时强制 kill
重要:如果应用需要优雅关闭(如处理完当前请求再退出),必须使用 exec 格式。
3.8 完全绕过 entrypoint
# 完全覆盖,包括 entrypoint
docker run --entrypoint redis-cli redis这样 ENTRYPOINT 和 CMD 都会被覆盖,直接执行 redis-cli。
第四章:多阶段构建详解
多阶段构建是 Docker 17.05+ 引入的特性,可以显著减小镜像体积。
4.1 为什么需要多阶段构建?
以 Python 应用为例:
- 编译阶段:需要 Python、pip、gcc、数据库驱动编译依赖
- 运行阶段:只需要 Python 运行时和已编译的依赖
传统方式需要在一个镜像里包含所有东西,体积很大。多阶段构建可以分离这两个阶段。
4.2 基础多阶段构建
# ============ 第一阶段:构建 ============
FROM node:20-alpine AS builder
WORKDIR /app
# 复制依赖文件并安装
COPY package*.json ./
RUN npm ci
# 复制源码并构建
COPY . .
RUN npm run build
# ============ 第二阶段:运行 ============
FROM node:20-alpine AS runner
WORKDIR /app
# 创建非 root 用户
RUN addgroup --system --gid 1001 nodejs && \
adduser --system --uid 1001 nextjs
# 从第一阶段复制构建产物
COPY --from=builder --chown=nextjs:nodejs /app/dist ./dist
COPY --from=builder --chown=nextjs:nodejs /app/node_modules ./node_modules
USER nextjs
EXPOSE 3000
CMD ["node", "dist/app.js"]4.3 Next.js 项目多阶段构建示例
# ============ 第一阶段:构建 ============
FROM node:20-alpine AS builder
WORKDIR /app
# 先复制依赖文件(利用 Docker 缓存)
COPY package*.json ./
RUN npm ci
# 复制源码并构建
COPY . .
RUN npm run build
# ============ 第二阶段:运行 ============
FROM node:20-alpine AS runner
WORKDIR /app
# 创建非 root 用户
RUN addgroup --system --gid 1001 nodejs && \
adduser --system --uid 1001 nextjs
# 复制构建产物
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
COPY --from=builder --chown=nextjs:nodejs /app/public ./public
USER nextjs
EXPOSE 3000
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"
CMD ["node", "server.js"]注意:Next.js 需要在
next.config.ts中开启 standalone 模式:const nextConfig = { output: 'standalone', };
4.4 Go 项目多阶段构建
Go 是静态编译语言,可以实现极小镜像:
# ============ 第一阶段:构建 ============
FROM golang:1.21-alpine AS builder
WORKDIR /app
# 复制源码
COPY . .
# CGO_ENABLED=0 表示静态编译,不依赖 libc
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .
# ============ 第二阶段:运行 ============
FROM scratch
# 只复制二进制文件
COPY --from=builder /app/main .
# 不需要任何运行时!
ENTRYPOINT ["./main"]最终镜像可以只有几 MB。
4.5 Java 项目多阶段构建
# ============ 第一阶段:构建 ============
FROM maven:3.9-eclipse-temurin-17 AS builder
WORKDIR /app
# 先复制依赖(利用缓存)
COPY pom.xml .
RUN mvn dependency:go-offline
# 复制源码并打包
COPY src ./src
RUN mvn package -DskipTests
# ============ 第二阶段:运行 ============
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
COPY --from=builder /app/target/myapp.jar ./app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]第五章:镜像优化实战
5.1 精简镜像原则
| 原则 | 操作 |
|---|---|
| 减少层数 | 多条 RUN 合并 |
| 清理缓存 | yum clean all -y / npm cache clean --force |
| 精简基础镜像 | 用 alpine、slim 版本 |
| 避免二手镜像 | 不从 ubuntu 再装 Python,用 python:3.9-alpine |
| 调整指令顺序 | 不变的放前面(利用缓存) |
| 用非 root 用户 | USER 指令 |
5.2 清理缓存详解
yum/dnf 清理
RUN yum install -y https://packages.microsoft.com/config/rhel/8/packages-microsoft-prod.rpm \
&& yum install -y dotnet-sdk-8.0 \
&& yum clean allnpm 清理
RUN npm ci --only=production && npm cache clean --forcepip 清理
RUN pip install --no-cache-dir -r requirements.txt5.3 利用 Docker 缓存
Docker 构建时会利用缓存,但如果某一行改变,之后的所有层都会重新构建。
优化技巧:把频繁变动的内容放后面,不变的内容放前面。
# 好的写法(不变的内容先处理)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . . # 代码变动,但 requirements 没变时,这行不会触发 npm install
# 不好的写法(每次代码变动都会重新安装依赖)
COPY . .
RUN pip install -r requirements.txt5.4 .dockerignore 文件
和 .gitignore 类似,构建时排除不需要的文件。
# .dockerignore
node_modules
.npm
.git
.next
*.log
.env
.env.*
!.env.example
.vscode
.idea
*.md
README.md
Dockerfile
docker-compose.yml第六章:镜像构建与运行
6.1 构建命令详解
# 基本构建
docker build -t myapp:1.0 .
# 指定 Dockerfile 路径
docker build -f /path/to/Dockerfile -t myapp:1.0 .
# 不使用缓存(强制重新构建)
docker build --no-cache -t myapp:1.0 .
# 传递构建参数
docker build --build-arg VERSION=1.0 --build-arg ENV=prod -t myapp:1.0 .
# 多平台构建
docker build --platform linux/amd64,linux/arm64 -t myapp:1.0 .
# 构建并指定输出
docker build -t myapp:1.0 -o ./dist .6.2 查看镜像信息
# 查看镜像列表
docker images
# 查看镜像构建历史
docker history myapp:1.0
# 查看镜像详细信息
docker inspect myapp:1.06.3 运行容器
# 基本运行
docker run myapp:1.0
# 后台运行
docker run -d myapp:1.0
# 端口映射
docker run -d -p 8080:3000 myapp:1.0
# 环境变量
docker run -e NODE_ENV=production -e PORT=8080 myapp:1.0
# 挂载卷
docker run -v /host/path:/container/path myapp:1.0
# 交互式运行
docker run -it myapp:1.0 /bin/sh
# 运行完自动删除
docker run --rm myapp:1.0
# 覆盖 entrypoint
docker run --entrypoint /bin/sh myapp:1.06.4 完整示例:Flask 应用
目录结构
myflask/
├── Dockerfile
├── requirements.txt
├── app.py
└── .dockerignore准备文件
# app.py
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello():
return 'Hello World!'
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)# requirements.txt
flaskDockerfile
FROM python:3.9-alpine
WORKDIR /app
# 先复制依赖文件(利用 Docker 缓存)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 再复制应用代码
COPY app.py .
EXPOSE 5000
# 开发模式直接运行
CMD ["python", "app.py"]构建并运行
# 构建
docker build -t myflask:1.0 .
# 运行
docker run -d -p 8080:5000 --name myapp myflask:1.0
# 验证
curl localhost:80806.5 完整示例:React 应用
# ============ 构建阶段 ============
FROM node:20-alpine AS builder
WORKDIR /app
# 复制依赖文件
COPY package*.json ./
RUN npm ci
# 复制源码并构建
COPY . .
RUN npm run build
# ============ 运行阶段 ============
FROM nginx:alpine
# 从构建阶段复制构建产物到 nginx 目录
COPY --from=builder /app/dist /usr/share/nginx/html
# nginx 配置文件
COPY nginx.conf /etc/nginx/nginx.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]# nginx.conf
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
}
}第七章:容器运维实践
7.1 容器生命周期
| 状态 | 说明 |
|---|---|
| Created | 基于镜像创建,文件系统已初始化,配置已加载,但主进程未启动 |
| Running | 主进程正在执行,资源消耗开始 |
| Paused | 主进程被 SIGSTOP 暂停,保留所有状态,不消耗 CPU |
| Exited | 主进程执行完毕或异常退出 |
| Restarting | 停止的容器重新启动 |
| Deleted | 容器被永久移除 |
7.2 常用容器命令
# 容器管理
docker ps # 查看运行中的容器
docker ps -a # 查看所有容器(包括已停止)
docker start myapp # 启动容器
docker stop myapp # 停止容器(发送 SIGTERM)
docker restart myapp # 重启容器
docker rm myapp # 删除容器(必须先 stop)
docker rm -f myapp # 强制删除容器
# 进入容器
docker exec -it myapp /bin/sh
docker exec -it myapp ls /app
# 查看日志
docker logs myapp
docker logs -f myapp # 实时跟踪
docker logs --tail 100 myapp # 最后 100 行
# 查看资源使用
docker stats myapp
docker stats --no-stream myapp # 只显示一次
# 复制文件
docker cp host/file myapp:/app/file
docker cp myapp:/app/file host/file
# 查看容器信息
docker inspect myapp # 详细信息
docker port myapp # 端口映射
docker top myapp # 容器内进程7.3 容器安全实践
使用非 root 用户
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser限制容器的 capabilities
# 只保留必要的 capabilities
docker run --cap-drop=all \
--cap-add=NET_BIND_SERVICE \
myapp使用只读文件系统
docker run --read-only myapp限制内存和 CPU
docker run -m 512m --memory-swap=1g \
--cpus=1.5 \
myapp7.4 日志管理
配置日志轮转
# 全局配置 /etc/docker/daemon.json
{
"log-driver": "json-file",
"log-opts": {
"max-size": "50m",
"max-file": "3"
}
}# 运行时指定
docker run --log-opt max-size=10m --log-opt max-file=3 myapp查看容器日志
# 实时跟踪
docker logs -f myapp
# 带时间戳
docker logs -t myapp
# 最后 N 行
docker logs --tail 100 myapp
# 过滤日志(需要 jq)
docker logs myapp 2>&1 | grep error7.5 资源限制详解
内存限制
# 限制内存为 512MB
docker run -m 512m myapp
# 限制内存为 256MB,swap 为 512MB
docker run -m 256m --memory-swap=512m myapp
# 关闭 swap
docker run -m 256m --memory-swappiness=0 myappCPU 限制
# 限制为 1.5 个 CPU
docker run --cpus=1.5 myapp
# 限制为 0.5 个 CPU
docker run --cpus=0.5 myapp
# 限制特定 CPU 核心
docker run --cpuset-cpus="0,1" myapp磁盘 IO 限制
# 限制读取速率 10MB/s
docker run --device-read-bps=/dev/sda:10m myapp
# 限制写入速率 10MB/s
docker run --device-write-bps=/dev/sda:10m myapp
# 限制 IOPS
docker run --device-read-iops=/dev/sda:100 myapp第八章:常见问题与解决方案
8.1 构建失败:找不到包
# 错误:镜像源超时
RUN apt-get update && apt-get install -y curl
# 解决:更换镜像源或增加超时时间
RUN sed -i 's|http://archive.ubuntu.com|https://mirrors.aliyun.com|g' /etc/apt/sources.list && \
apt-get update && apt-get install -y curl8.2 构建失败:权限问题
# 错误:COPY 到系统目录
COPY . /usr/local/app/
# 解决:创建目录或使用非 root 用户
RUN mkdir -p /app && chown -R appuser:appgroup /app
COPY --chown=appuser:appgroup . /app8.3 容器启动失败:端口被占用
# 查看端口占用
netstat -tlnp | grep 8080
# 或
ss -tlnp | grep 8080
# 使用其他端口
docker run -p 8081:8080 myapp8.4 容器内中文乱码
ENV LANG=C.UTF-8
ENV LC_ALL=C.UTF-88.5 构建缓存失效问题
# 问题:每次构建都重新安装依赖(因为 requirements.txt 内容变化)
COPY . .
RUN pip install -r requirements.txt
# 解决:先复制 requirements.txt,安装完成后再复制代码
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .8.6 多架构构建
# 启用 experimental 功能
export DOCKER_CLI_EXPERIMENTAL=enabled
# 查看构建机器支持的架构
docker buildx ls
# 创建新的 builder
docker buildx create --name mybuilder
docker buildx use mybuilder
# 构建多架构镜像
docker buildx build --platform linux/amd64,linux/arm64 -t myapp:1.0 --push .总结
Docker 容器技术通过 Linux 内核的 Namespace 实现资源隔离、CGroup 实现资源限制、UnionFS 实现分层存储,三者配合构成了容器运行时的基础。
编写 Dockerfile 的核心原则:
- 清晰简洁:使用 WORKDIR、避免过长的 RUN 命令
- 利用缓存:合理安排指令顺序
- 最小化:选择精简基础镜像、清理缓存、不安装不必要的依赖
- 安全第一:使用非 root 用户、必要时限制 capabilities
- 多阶段构建:分离构建环境和运行环境,减小镜像体积
理解这些底层原理和最佳实践,对于进行故障排查、性能优化和安全加固都至关重要。