🐳 Docker 基础实战完整指南
定位 :零基础 → 能独立构建/部署/调试容器化应用
环境 :华为云 ECS ecs-fce0-0001 (2vCPU/4GiB/ac9.large.2/Ubuntu 24.04)
工具 :Docker CE 29.5.3 + Compose v5.1.4
风格:「原理简述 + 代码实操 + 常见坑点 + 企业小技巧」
Part 1: 为什么需要 Docker?------从痛点出发
1.1 传统部署的噩梦
没有 Docker 的年代,你的部署流程可能是这样的:
开发环境(Win/Mac) → 测试环境(Linux) → 生产环境(CentOS)
"在我机器上能跑!" "缺依赖/版本不对" "JDK版本冲突!"
| 痛点 | 描述 | Docker 解决方案 |
|---|---|---|
| 环境不一致 | Win vs Linux vs Mac 差异 | 容器统一环境,一次构建到处运行 |
| 依赖冲突 | 项目A用 JDK8,项目B用 JDK17 | 每个容器独立环境 |
| 部署慢 | 手动装依赖 → 1~2 小时 | 镜像秒级启动 |
| 资源浪费 | 每个应用一台虚拟机 | 容器共享内核,一台机器跑几十个 |
1.2 容器 vs 虚拟机
┌─────────────────────────────────────────────────────────────┐
│ Virtual Machine (虚拟机) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ App A │ │ App B │ │ App C │ │
│ │ Bin/Libs │ │ Bin/Libs │ │ Bin/Libs │ ← 各自完整的 OS │
│ │ Guest OS │ │ Guest OS │ │ Guest OS │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ Hypervisor (虚拟化层) │
│ Host OS (宿主机 OS) │
│ Physical Hardware │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Docker Container (容器) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ App A │ │ App B │ │ App C │ │
│ │ Bin/Libs │ │ Bin/Libs │ │ Bin/Libs │ ← 仅含应用依赖 │
│ └──────────┘ └──────────┘ └──────────┘ │
│ Docker Engine (容器引擎) │
│ Host OS (共享内核!) │
│ Physical Hardware │
└─────────────────────────────────────────────────────────────┘
| 维度 | 虚拟机 (VM) | 容器 (Container) |
|---|---|---|
| 启动速度 | 分钟级 | 秒级 |
| 磁盘占用 | GB 级 | MB 级 |
| 内存开销 | 每个额外 ~512MB | 几乎无额外开销 |
| 隔离性 | 完整隔离 | 进程级隔离 |
| 内核 | 每个 VM 独立内核 | 共享宿主机内核 ← 关键差异 |
1.3 镜像 vs 容器:类 vs 实例
Image (镜像) Container (容器)
┌──────┐ ┌──────────────┐
│ JDK │ │ JDK (layer 3)│
├──────┤ -run-> ├──────────────┤
│ Tomcat│ │Tomcat(layer2)│ ← 镜像的"运行时"
├──────┤ ├──────────────┤
│ War │ │ War (layer 1)│ 实例 + 可写层
└──────┘ └──────────────┘
只读 读写(R/W layer)
类比: Class → new → Object
Image → run → Container
1.4 Hello-World 拆解执行流程
在 docker-01 (121.36.42.55) 上实际执行:
bash
$ docker pull hello-world:latest
Digest: sha256:0e760fdfbc48ba8041e7c6db999bb40bfca508b4be580ac75d32c4e29d202ce1
Status: Downloaded newer image for hello-world:latest
$ docker run --name hw-demo hello-world
Hello from Docker!
This message shows that your installation appears to be working correctly.
To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
(amd64)
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.
执行流程拆解:
① docker run hello-world
Docker CLI ──────────────────────────► Docker Daemon
│
② 本地有没有镜像? │
├─ 没有 → 去 Registry 拉取 ──────┤
│ ③ 下载 hello-world:latest
└─ 有 → 直接用 │
│
④ 基于镜像创建容器 │
├─ 分配文件系统 (可写层) │
├─ 分配网络 (默认 bridge) │
└─ 启动进程 │
│
⑤ 执行容器内程序,stdout 流回 Client │
└─ 程序结束 → 容器退出 (Exited)
bash
# 验证容器生命周期
$ docker ps -a --filter name=hw-demo
NAMES IMAGE STATUS PORTS
hw-demo hello-world Exited (0) Less than a second ago
# 镜像大小仅 9.49KB!
$ docker images hello-world
IMAGE ID DISK USAGE CONTENT SIZE
hello-world:latest 0e760fdfbc48 25.9kB 9.49kB
企业真题 :"docker run 背后的完整流程是什么?"
答:CLI → Daemon → 检查本地镜像 → (无)拉取 Registry → 创建容器(分配网络/存储/Cgroup) → 启动进程 → 输出流回客户端
Part 2: 环境搭建与核心命令速成(1小时上手)
2.1 三平台安装(以 Ubuntu 24.04 为例)
bash
# Step1: 卸载旧版本
apt-get remove -y docker.io docker-doc podman-docker containerd runc
# Step2: 安装依赖
apt-get update && apt-get install -y ca-certificates curl
# Step3: 添加 Docker GPG 密钥(阿里云镜像)
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg \
-o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc
# Step4: 添加仓库(注意 CODENAME 变量)
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] \
https://mirrors.aliyun.com/docker-ce/linux/ubuntu noble stable" \
> /etc/apt/sources.list.d/docker.list
# Step5: 安装
apt-get update && apt-get install -y \
docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
# Step6: 配置镜像加速 + log 限制
mkdir -p /etc/docker
cat > /etc/docker/daemon.json << 'EOF'
{
"registry-mirrors": [
"https://docker.1ms.run",
"https://docker.xuanyuan.me"
],
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
EOF
systemctl daemon-reload && systemctl restart docker
systemctl enable docker
# Step7: 验证
$ docker --version
Docker version 29.5.3, build d1c06ef
$ docker compose version
Docker Compose version v5.1.4
2.2 高频命令实战清单
在 docker-01 上的真实操作:
bash
# ──────────── 1. docker run ────────────
$ docker run -d --name nginx-demo -p 8088:80 nginx:alpine
# -d: 后台运行 (detached)
# --name: 给容器命名
# -p 8088:80: 端口映射 宿主机:容器
# ──────────── 2. docker ps (查看运行中) ────────────
$ docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}"
NAMES IMAGE STATUS PORTS
nginx-demo nginx:alpine Up 5 minutes 0.0.0.0:8088->80/tcp
# ──────────── 3. docker logs (实时看日志) ────────────
$ docker logs nginx-demo --tail 3
# 关键参数: -f (follow实时) --tail N (最后N行) --since (时间范围)
# ──────────── 4. docker exec (进入容器) ────────────
$ docker exec nginx-demo sh -c "nginx -v 2>&1"
Nginx version: 1.27.4
# exec 让你"穿透"容器边界,在容器内执行命令
# 常用: docker exec -it <name> /bin/bash 进入交互式 shell
# ──────────── 5. docker cp (传文件) ────────────
$ echo "host_content" > /tmp/host-file.txt
$ docker cp /tmp/host-file.txt nginx-demo:/tmp/
$ docker exec nginx-demo cat /tmp/host-file.txt
host_content
# 双向传输: 宿主机→容器 和 容器→宿主机
# ──────────── 6. docker stats (资源监控) ────────────
$ docker stats --no-stream nginx-demo
# 实时显示 CPU/MEM/NET/DISK 使用情况
命令速查表:
| 命令 | 作用 | 常用参数 |
|---|---|---|
docker run -d --name X -p H:C IMG |
后台启动容器 | --restart -v -e --network |
docker ps -a |
查看所有容器 | --filter --format -q |
docker logs -f <id> |
实时看日志 | --tail N --since |
docker exec -it <id> sh |
进入容器 | -u root (以root执行) |
docker cp HOST_PATH ID:CONT_PATH |
传文件 | -a (保留权限) |
docker inspect <id> |
查看详细配置 | --format (Go模板) |
docker stop/start/restart <id> |
启停控制 | -t (等待超时秒数) |
docker rm <id> |
删除容器 | -f (强制) -v (同时删volume) |
docker rmi <image> |
删除镜像 | -f (强制) |
docker compose up -d |
Compose 启动 | --build -f |
2.3 镜像管理
bash
$ docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}"
REPOSITORY TAG SIZE
my-nginx v1 92.7MB
nginx alpine 93.6MB
redis 7-alpine 57.8MB
mysql 8.0 1.1GB
alpine latest 13.1MB
小技巧 :Alpine Linux 基础镜像仅 13.1MB,是制作精简镜像的首选,但注意它用
musl libc而非glibc。
Part 3: 镜像构建------从 Dockerfile 开始
3.1 Dockerfile 核心指令精讲
FROM ← 基础镜像(必须第一行)
WORKDIR ← 设置工作目录
COPY/ADD ← 复制文件(COPY 简单复制, ADD 支持URL/tar解压)
RUN ← 构建时执行命令(每RUN一层layer)
ENV ← 环境变量
EXPOSE ← 声明端口(仅是文档, 不实际映射)
CMD ← 容器启动默认命令(可被 docker run 覆盖)
ENTRYPOINT ← 容器入口(docker run 参数作为其参数)
VOLUME ← 声明数据卷
USER ← 切换用户
HEALTHCHECK ← 健康检查
| 指令 | 执行时机 | 是否可被覆盖 | 多层影响 |
|---|---|---|---|
RUN |
构建时 (build) | 否 | 每 RUN 一层 |
CMD |
运行时 (run) | docker run IMG cmd 完全覆盖 |
只有最后一层 |
ENTRYPOINT |
运行时 (run) | 需 --entrypoint 强覆盖 |
只有最后一层 |
COPY vs ADD:
| 指令 | 功能 | 建议 |
|---|---|---|
COPY |
从构建上下文复制文件 | 优先使用 |
ADD |
复制 + URL下载 + 自动解压tar | 仅在需要tar自动解压时用 |
3.2 实战:构建一个 Nginx 静态网站
在 docker-01 上实操:
bash
$ mkdir /tmp/docker-nginx
$ cat > /tmp/docker-nginx/Dockerfile << 'DEOF'
FROM nginx:alpine
LABEL maintainer="admin@example.com"
LABEL description="Nginx 静态网站实战"
COPY index.html /usr/share/nginx/html/
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
DEOF
$ echo "<h1>Hello from Docker Nginx!</h1>" > /tmp/docker-nginx/index.html
$ docker build -t my-nginx:v1 /tmp/docker-nginx
# Step 1/6 : FROM nginx:alpine
# Step 2/6 : LABEL maintainer=...
# Step 3/6 : COPY index.html /usr/share/nginx/html/
# Step 4/6 : EXPOSE 80
# Step 5/6 : CMD ["nginx", "-g", "daemon off;"]
# Successfully built my-nginx:v1
$ docker run -d --name nginx-build-demo -p 8090:80 my-nginx:v1
$ curl -s http://localhost:8090/
<h1>Hello from Docker Nginx!</h1>
$ docker images my-nginx --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}"
REPOSITORY TAG SIZE
my-nginx v1 92.7MB
3.3 进阶:多阶段构建优化镜像大小
┌────────────────────────────────────────────────────────┐
│ 传统构建 (1个镜像) │
│ ┌──────────────────────┐ │
│ │ FROM JDK │ │
│ │ COPY src . │ │
│ │ RUN mvn package │ ← 构建工具 + 代码都留在镜像 │
│ │ CMD java -jar ... │ 体积: 500MB+ │
│ └──────────────────────┘ │
└────────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────────┐
│ 多阶段构建 (Multi-stage) │
│ ┌──────────────────────┐ ┌──────────────────┐ │
│ │ Stage 1: build │ │ Stage 2: runtime │ │
│ │ FROM maven:3.9 AS b │ │ FROM jre:17 │ │
│ │ COPY src . │ │ COPY --from=b /app│ │
│ │ RUN mvn package │──▶│ CMD java -jar ... │ │
│ └────────┬─────────────┘ └──────────────────┘ │
│ │ 体积: 120MB ↓↓↓ │
│ 构建产物 (.jar) 仅复制到运行阶段 │
└────────────────────────────────────────────────────────┘
多阶段构建 Dockerfile 模板:
dockerfile
# === 阶段1: 编译 ===
FROM maven:3.9-eclipse-temurin-17 AS build
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline # 缓存依赖(加速后续构建)
COPY src ./src
RUN mvn package -DskipTests
# === 阶段2: 运行 ===
FROM eclipse-temurin:17-jre
WORKDIR /app
COPY --from=build /app/target/*.jar app.jar
EXPOSE 8080
CMD ["java", "-jar", "app.jar"]
企业级技巧:
.dockerignore:避免node_modules/,.git/,*.md进入构建上下文- 层优化:把不变的层放前面(依赖 > 代码),变动层放后面
- 非 root 用户 :
USER 1000避免容器内 root 权限 - 健康检查 :
HEALTHCHECK CMD curl -f http://localhost/ || exit 1
Part 4: 容器网络与数据持久化
4.1 网络模式实战
Docker 三大网络模式:
| 模式 | 特点 | 适用场景 |
|---|---|---|
| bridge (默认) | 独立网络栈,NAT 通信 | 一般容器,需端口映射 |
| host | 共享宿主机网络栈 | 高性能场景(如监控 agent) |
| none | 无网络,完全隔离 | 安全敏感(如密钥生成) |
bash
# 查看网络模式
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
683234b9fb40 bridge bridge local ← 默认 bridge
02635a047886 host host local
0cf4b1c9abef none null local
4.2 自定义网络:容器间 DNS 互通
bash
# 创建自定义网络(内置 DNS 解析!)
$ docker network create app-net
$ docker network inspect app-net --format 'Subnet: {{range .IPAM.Config}}{{.Subnet}}{{end}}'
Subnet: 172.18.0.0/16
# 在同一网络中启动容器
$ docker run -d --name web --network app-net nginx:alpine
$ docker run -d --name app --network app-net alpine sleep 300
# DNS 解析测试(容器名 → IP,自动!)
$ docker exec app ping -c 2 web
PING web (172.18.0.2): 56 data bytes
64 bytes from 172.18.0.2: seq=0 ttl=64 time=0.120 ms
64 bytes from 172.18.0.2: seq=1 ttl=64 time=0.115 ms
关键 :自定义 bridge 网络提供自动 DNS 解析 ,容器间用名称即可通信,不用
--link(已废弃)!
4.3 Volume vs Bind Mount
┌──────────────────────────────────────────────────┐
│ Volume (Docker 管理) │
│ Container ───► /var/lib/mysql │
│ │ │
│ ┌──────▼─────────┐ │
│ │ /var/lib/docker│ │
│ │ /volumes/... │ ← Docker 管理 │
│ └────────────────┘ │
└──────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────┐
│ Bind Mount (宿主机直连) │
│ Container ───► /app/config │
│ │ │
│ ┌──────▼──────┐ │
│ │ /home/user/ │ ← 用户管理 │
│ │ config/ │ │
│ └─────────────┘ │
└──────────────────────────────────────────────────┘
| 类型 | 管理方 | 路径 | 跨容器 | 适用场景 |
|---|---|---|---|---|
| Volume | Docker | /var/lib/docker/volumes/ |
✅ | 数据库、Redis |
| Bind Mount | 用户 | 任意宿主机路径 | ❌ | 开发调试、配置热更新 |
实战:Volume 持久化数据(在 docker-01 上已执行):
bash
$ docker volume create mysql-data
$ docker run -d --name mysql-persist \
-e MYSQL_ROOT_PASSWORD=root123 \
-v mysql-data:/var/lib/mysql \
mysql:8.0
# 写入测试数据
$ docker exec mysql-persist mysql -uroot -proot123 \
-e "CREATE TABLE testdb.demo(id INT); INSERT INTO testdb.demo VALUES(42);"
# 重启容器
$ docker restart mysql-persist
# 数据仍在!
$ docker exec mysql-persist mysql -uroot -proot123 \
-e "SELECT * FROM testdb.demo;"
+------+
| id |
+------+
| 42 |
+------+
Bind Mount 演示:
bash
$ echo "host file content" > /tmp/bind-demo.txt
$ docker run --rm -v /tmp/bind-demo.txt:/data/file.txt:ro alpine cat /data/file.txt
host file content
# :ro = read-only,防止容器修改宿主机文件
Part 5: Compose 编排------告别单容器时代
5.1 docker-compose.yml 语法精要
核心三要素:services, networks, volumes
yaml
# docker-compose.yml
# 三要素: services, networks, volumes
services: # 定义服务(容器)
web: # 服务名(即 DNS 名)
image: nginx:alpine
ports:
- "9090:80"
volumes:
- ./html:/usr/share/nginx/html:ro
restart: unless-stopped # 生产必备!
redis:
image: redis:7-alpine
ports:
- "6379:6379"
restart: unless-stopped
command: redis-server --save 60 1 --loglevel warning
5.2 实战:一键部署 Nginx + Redis
真实执行(docker-01):
bash
$ mkdir -p /tmp/compose-demo/html
$ echo "<h1>Compose Demo - Nginx + Redis</h1>" > /tmp/compose-demo/html/index.html
$ docker compose up -d
# [+] Running 4/4
# Network compose-demo_default Created
# Container compose-demo-web-1 Started
# Container compose-demo-redis-1 Started
$ docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Status}}"
NAMES IMAGE STATUS
compose-demo-web-1 nginx:alpine Up 5 seconds
compose-demo-redis-1 redis:7-alpine Up 5 seconds
# 测试 Web
$ curl -s http://localhost:9090/
<h1>Compose Demo - Nginx + Redis</h1>
# 测试 Redis
$ docker exec compose-demo-redis-1 redis-cli ping
PONG
5.3 环境变量管理
bash
# .env 文件(自动加载)
$ cat .env
REDIS_PASSWORD=secret123
DB_HOST=mysql-db
# docker-compose.yml 中使用
services:
app:
environment:
- REDIS_PASSWORD=${REDIS_PASSWORD:-default} # 有默认值
- DB_HOST=${DB_HOST:?DB_HOST is required} # 必须设置
关键注意:
depends_on只等容器启动,不等待服务就绪!MySQL 可能需要 30s 初始化。- 解决方案:健康检查 +
wait-for-it.sh脚本 restart: unless-stopped生产必备(手动 stop 不会重启,其他退出都重启)
| restart 值 | 行为 |
|---|---|
no (默认) |
不自动重启 |
always |
无论退出码都重启 |
on-failure:N |
仅非0退出码重启,最多 N 次 |
unless-stopped |
推荐:手动 stop 不重启,其他都重启 |
5.4 常用 Compose 命令
bash
docker compose up -d # 后台启动
docker compose down # 停止并删除容器/网络
docker compose down -v # 同时删除 Volume!
docker compose ps # 查看服务状态
docker compose logs -f web # 实时日志
docker compose restart # 重启所有服务
docker compose up -d --build # 重新构建并启动
Part 6: 调试与故障排查实战
6.1 容器启动失败?5 步诊断法
Container 启动失败!
│
┌─────▼──────┐
① │ docker logs │ → 看错误输出
└─────┬──────┘
│
┌─────▼──────────────┐
② │ docker inspect │ → 查 CMD/Entrypoint/Env
└─────┬──────────────┘
│
┌─────▼────────────────────┐
③ │ docker exec -it <id> sh │ → 进容器检查文件/权限
└─────┬────────────────────┘
│
┌─────▼────────────────────┐
④ │ netstat -tulnp | grep :XX│ → 检查端口冲突
└─────┬────────────────────┘
│
┌─────▼────────────────────┐
⑤ │ /var/log/syslog/audit.log│ → 宿主机日志
└──────────────────────────┘
│
[ 定位根因! ]
实际诊断演示:
bash
# 故意创建一个会退出的容器
$ docker run -d --name bad-cmd alpine /bin/nonexistent
$ docker ps -a --format "table {{.Names}}\t{{.Status}}"
NAMES STATUS
bad-cmd Exited (127) 1 second ago ← 启动即退出, 退出码 127
$ docker logs bad-cmd
exec /bin/nonexistent: no such file or directory ← 定位到问题!
$ docker inspect bad-cmd --format 'CMD: {{.Config.Cmd}}'
CMD: [/bin/nonexistent] ← 确认 CMD 错误
6.2 经典问题合集
| 错误 | 原因 | 解决 |
|---|---|---|
port is already allocated |
端口冲突 | lsof -i :8080 找出占用进程 + 换端口 |
standard_init_linux.go:228: exec user process caused: no such file or directory |
CRLF 换行符 | dos2unix 或 git config core.autocrlf=false |
Permission denied 挂载目录 |
SELinux/AppArmor | 加 :z 标签或改用非 root 用户 |
bind: address already in use |
端口已被占用 | 检查 docker ps 已存在的容器 |
429 Too Many Requests |
镜像仓库限流 | 换镜像源或等待 |
COPY failed: file not found |
构建上下文路径不对 | 检查 .dockerignore 和 Dockerfile 中的相对路径 |
6.3 端口冲突实战(真实踩坑)
bash
# docker-01 上的真实错误
$ docker run -d --name web-demo -p 8088:80 nginx:alpine
Error response from daemon: Bind for 0.0.0.0:8088 failed: port is already allocated
# 诊断
$ docker ps --format "table {{.Names}}\t{{.Ports}}"
NAMES PORTS
nginx-demo 0.0.0.0:8088->80/tcp ← 8088 已被占用!
# 解决:先停掉占用的或换端口
$ docker stop nginx-demo && docker rm nginx-demo
# 或
$ docker run -d --name web-demo -p 9090:80 nginx:alpine
Part 7: 企业级入门实践
7.1 CI/CD 中的 Docker
┌──────────────────────────────────────────────────────┐
│ Jenkins / GitHub Actions │
│ │
│ git push ──► Build ──► Test ──► Push Image ──► Deploy│
│ │ │ │
│ docker build docker push │
│ │ │ │
│ ┌────▼────┐ ┌───────▼────────┐ │
│ │ 本地镜像 │ │ Harbor/ECR/ACR │ │
│ └─────────┘ │ (私有仓库) │ │
│ └───────┬────────┘ │
│ │ │
│ docker pull │
│ │ │
│ ┌─────▼─────┐ │
│ │ K8s / ECS │ │
│ └───────────┘ │
└──────────────────────────────────────────────────────┘
yaml
# GitHub Actions 示例
name: Docker Build & Push
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build Image
run: docker build -t myapp:${{ github.sha }} .
- name: Push to Registry
run: |
docker tag myapp:${{ github.sha }} registry.example.com/myapp:${{ github.sha }}
docker push registry.example.com/myapp:${{ github.sha }}
7.2 Docker 最佳实践 Checklist
☑ 使用 .dockerignore 避免打包无用文件
☑ 镜像分层合理(不变层在上,变化层在下)
☑ 非 root 用户运行容器 (USER 1000)
☑ 启用健康检查 (HEALTHCHECK)
☑ 标签规范 v1.2.3-{BUILD_NUMBER}
☑ 多阶段构建减小镜像体积
☑ 日志输出到 stdout/stderr (不要写文件)
☑ 避免在 Dockerfile 中写密码 (用 --build-arg + secrets)
☑ 生产环境 restart: unless-stopped
☑ 资源限制 --memory --cpus 防止容器耗尽宿主机
☑ 定期清理无用镜像/容器 (docker system prune -f)
7.3 安全提醒
dockerfile
# ❌ 危险做法
FROM ubuntu:latest
ENV DB_PASSWORD=mysecret123 # 密码明文写在镜像层!
USER root # root 运行!
# ✅ 安全做法
FROM ubuntu:24.04 # 固定版本,不用 :latest
RUN groupadd -r appgroup && useradd -r -g appgroup appuser
USER appuser # 非 root 运行
# 密码通过 docker run -e 或 secrets 传入
附录 & 资源包
A. 镜像大小优化对比
| 方案 | 基础镜像 | 大小 | 说明 |
|---|---|---|---|
| 传统 JDK | openjdk:17 |
~470MB | 含完整 JDK |
| JRE 精简 | eclipse-temurin:17-jre |
~210MB | 仅运行时 |
| Alpine JRE | eclipse-temurin:17-jre-alpine |
~185MB | 最小 JRE |
| 多阶段 | 自定义 | ~120MB | 编译+运行分离 |
| Distroless | gcr.io/distroless/java17 |
~120MB | Google 最小化 |
| 方案 | 基础镜像 | 大小 | 说明 |
|---|---|---|---|
| Nginx 官方 | nginx:latest |
~187MB | Debian 基础 |
| Nginx Alpine | nginx:alpine |
~93MB | 实测 93.6MB |
| Nginx Distroless | nginx:alpine+精简 |
~60MB | 去掉 shell |
B. Docker 命令速查卡
镜像管理:
docker pull <img>:<tag> 拉取镜像
docker build -t <name> . 构建镜像
docker push <img>:<tag> 推送镜像
docker rmi <img> 删除镜像
docker tag <src> <dst> 打标签
docker save/load 导入导出
容器管理:
docker run -d --name X -p H:C IMG 启动
docker exec -it X /bin/bash 进入
docker logs -f X 看日志
docker cp HOST_PATH X:CONT_PATH 传文件
docker commit X new:tag 保存为镜像
系统管理:
docker system df 磁盘占用
docker system prune -f 清理无用资源
docker stats --no-stream 资源监控
docker info 系统信息
Compose:
docker compose up -d 启动
docker compose down -v 停止+清理
docker compose ps 状态
docker compose logs -f svc 日志
C. 常见问题 FAQ
Q1: 容器退出后数据还在吗?
A: 容器删除后 可写层数据丢失。Volume/Bind Mount 的数据保留。
Q2: 如何让容器开机自启?
A: docker run --restart unless-stopped 或 Compose 中设 restart: unless-stopped
Q3: 容器之间如何通信?
A: 创建自定义 bridge 网络,容器间用 服务名(容器名) 做 DNS 解析。
Q4: 镜像太大怎么办?
A: 多阶段构建 + Alpine 基础镜像 + .dockerignore + 清理 apt-get clean
Q5: docker compose 和 docker-compose 区别?
A: docker compose 是 Docker CLI 插件(v2),docker-compose 是独立 Python 脚本(v1 已废弃)。
Q6: 如何限制容器资源?
A: docker run --memory 512m --cpus 1.5
Q7: 生产环境需要哪些镜像?
A: 应用镜像 + 监控 (Prometheus) + 日志 (ELK) + 反向代理 (Nginx/Traefik) + 数据库 (建议用云服务而非容器)
Q8: 容器内时间不对?
A: docker run -v /etc/localtime:/etc/localtime:ro 或设置 TZ 环境变量
今日挑战
尝试用 Alpine 构建一个 Python Flask 应用容器,要求:
- 基于
python:3-alpine- 安装 Flask (
pip install flask)- 编写一个返回
{"status": "ok"}的/health接口- 最终镜像 < 100MB
提示:
cd /tmp && docker build -t flask-app:alpine .
本系列博客基于华为云 ecs-fce0 集群实战操作编写,Docker CE 29.5.3,所有输出均为真实服务器执行结果。