一、Docker 是什么?——一句话就能说清,但我偏要说一万字
1.1 从"在我电脑上能跑"说起
每个程序员的职业生涯中,都至少说过一次这句话:
"在我电脑上是好的啊?!"
这句话的杀伤力,堪比产品经理的"这个需求很简单"。
Docker 的出现,就是为了终结这场旷日持久的甩锅大战。它的核心思想很简单:把你的代码、依赖、配置、环境,统统打包成一个标准化的箱子(容器),扔到哪里都能跑。
1.2 虚拟机 vs 容器:一图胜千言
(VMware/KVM)"] subgraph VM1["虚拟机 1"] GOS1["Guest OS
完整操作系统 🐘"] LIB1["Bins/Libs"] APP1["App A"] end subgraph VM2["虚拟机 2"] GOS2["Guest OS
完整操作系统 🐘"] LIB2["Bins/Libs"] APP2["App B"] end HW1 --> HOS1 --> HYP HYP --> VM1 HYP --> VM2 end subgraph CT["容器架构 🏃 轻量级"] HW2["物理硬件"] HOS2["宿主机 OS"] DCK["Docker Engine"] subgraph C1["容器 1"] CLIB1["Bins/Libs"] CAPP1["App A"] end subgraph C2["容器 2"] CLIB2["Bins/Libs"] CAPP2["App B"] end HW2 --> HOS2 --> DCK DCK --> C1 DCK --> C2 end
| 对比维度 | 虚拟机 | 容器 |
|---|---|---|
| 启动速度 | 分钟级 😴 | 秒级 ⚡ |
| 资源占用 | GB 级 | MB 级 |
| 隔离性 | 强(硬件级) | 较强(内核级) |
| 性能损耗 | 10-20% | ≈ 0% |
| 心情影响 | 等到怀疑人生 | 快到怀疑人生 |
1.3 Docker 核心概念三件套
———
造房子的图纸"] -->|docker build| IMG["📦 Image 镜像
———
造好的毛坯房"] IMG -->|docker run| CT["🏠 Container 容器
———
住进去的房子"] CT -->|docker commit| IMG2["📦 New Image
———
装修后拍照存档"] REG["☁️ Registry 仓库
———
房产中介
(Docker Hub)"] IMG -->|docker push| REG REG -->|docker pull| IMG
用人话说:
- Dockerfile:菜谱,告诉 Docker 怎么做这道菜
- Image:做好的预制菜,冷冻保存,随时加热
- Container:把预制菜热好端上桌,正在被吃的那盘
- Registry:预制菜超市(Docker Hub)
二、安装 Docker——第一个放弃的机会
2.1 各平台安装体验一览
拖进 Applications"] MAC --> MAC_OK["✅ 一分钟搞定"] MAC_OK --> MAC_BUT["但是...吃掉你 4GB 内存 💀"] OS -->|"Windows"| WIN{"你有 WSL2 吗?"} WIN -->|"有"| WIN_OK["Docker Desktop for Windows"] WIN -->|"没有"| WSL["先装 WSL2..."] WSL --> BIOS["先开 BIOS 虚拟化..."] BIOS --> REBOOT["重启电脑..."] REBOOT --> PRAY["祈祷不蓝屏 🙏"] PRAY --> WIN_OK WIN_OK --> WIN_BUT["但是...Hyper-V 和 VMware 打架 💀"] OS -->|"Linux"| LINUX["一行命令搞定"] LINUX --> LINUX_CMD["curl -fsSL get.docker.com | sh"] LINUX_CMD --> LINUX_OK["✅ 完美"] LINUX_OK --> LINUX_BUT["但是...你选择了 Linux 💀"]
2.2 Linux 安装(正经版)
# Ubuntu / Debian
# 1. 卸载旧版本(如果有的话)
sudo apt-get remove docker docker-engine docker.io containerd runc
# 2. 安装依赖
sudo apt-get update
sudo apt-get install ca-certificates curl gnupg lsb-release
# 3. 添加 Docker 官方 GPG Key
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \
sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
# 4. 设置仓库
echo "deb [arch=$(dpkg --print-architecture) \
signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# 5. 安装 Docker Engine
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io \
docker-buildx-plugin docker-compose-plugin
# 6. 验证安装
sudo docker run hello-world
# 7. 把自己加到 docker 组(告别 sudo)
sudo usermod -aG docker $USER
# 然后重新登录,或者 newgrp docker🎉 如果你看到了 Hello from Docker!,恭喜你,你已经完成了整个 Docker 学习过程中最简单的一步。三、Docker 基础操作——幸福的时光总是短暂的
3.1 Docker 命令全景图
拉取镜像"] BUILD["docker build
构建镜像"] IMAGES["docker images
查看镜像列表"] RMI["docker rmi
删除镜像"] TAG["docker tag
给镜像打标签"] PUSH["docker push
推送到仓库"] end subgraph "容器生命周期 🏠" RUN["docker run
创建并启动"] START["docker start
启动已停止的"] STOP["docker stop
优雅停止"] KILL["docker kill
强制停止"] RM["docker rm
删除容器"] RESTART["docker restart
重启"] end subgraph "容器交互 🔧" EXEC["docker exec
进入容器"] LOGS["docker logs
查看日志"] CP["docker cp
文件复制"] INSPECT["docker inspect
查看详情"] TOP["docker top
查看进程"] STATS["docker stats
资源监控"] end subgraph "清理操作 🧹" PRUNE_C["docker container prune
清理停止的容器"] PRUNE_I["docker image prune
清理悬空镜像"] PRUNE_A["docker system prune
核弹级清理 💣"] end
3.2 最常用的命令(背下来,不亏)
# ============ 镜像操作 ============
# 搜索镜像(Docker Hub 上找找看)
docker search nginx
# 拉取镜像(不指定 tag 默认 latest)
docker pull nginx:1.25
docker pull redis:7-alpine # alpine 版本更小巧
# 查看本地镜像
docker images
docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}"
# 删除镜像
docker rmi nginx:1.25
docker rmi $(docker images -q -f "dangling=true") # 删除所有悬空镜像
# ============ 容器操作 ============
# 运行容器(最基础)
docker run nginx
# 运行容器(人类能用的版本)
docker run -d \ # 后台运行(detached)
--name my-nginx \ # 给容器取个名字
-p 8080:80 \ # 端口映射:宿主机8080 -> 容器80
-v /data/html:/usr/share/nginx/html \ # 目录挂载
-e TZ=Asia/Dubai \ # 环境变量(迪拜时区 🇦🇪)
--restart unless-stopped \ # 重启策略
nginx:1.25
# 查看运行中的容器
docker ps
docker ps -a # 包含已停止的
# 进入容器(救命用)
docker exec -it my-nginx /bin/bash
docker exec -it my-nginx sh # 如果容器里没有 bash
# 查看日志(排错用)
docker logs my-nginx
docker logs -f my-nginx # 实时跟踪
docker logs --tail 100 my-nginx # 最后100行
docker logs --since 30m my-nginx # 最近30分钟
# 容器与宿主机之间复制文件
docker cp my-nginx:/etc/nginx/nginx.conf ./nginx.conf
docker cp ./index.html my-nginx:/usr/share/nginx/html/
# 停止与删除
docker stop my-nginx
docker rm my-nginx
docker rm -f my-nginx # 强制删除(运行中也删)3.3 端口映射——最容易翻车的地方
💡 记忆口诀:
-p 宿主:容器,左边是外面,右边是里面。就像写收件地址一样——先写大的(外面),再写小的(里面)。如果你记反了,恭喜你获得了一个花费两小时 debug 的机会。
四、Dockerfile——入门的分水岭
4.1 Dockerfile 指令速查
基础镜像
👶 我从哪来"] --> WORKDIR["WORKDIR
工作目录
📂 我在哪干活"] WORKDIR --> COPY["COPY / ADD
复制文件
📋 把东西搬进来"] COPY --> RUN["RUN
执行命令
🔨 安装依赖/编译"] RUN --> ENV["ENV
环境变量
⚙️ 配置参数"] ENV --> EXPOSE["EXPOSE
暴露端口
🚪 开个门"] EXPOSE --> CMD["CMD / ENTRYPOINT
启动命令
🚀 容器启动时干啥"] style FROM fill:#e1f5fe style CMD fill:#c8e6c9
4.2 一个 Spring Boot 项目的 Dockerfile 进化史
第一版:能跑就行(新手村)
FROM openjdk:17
COPY target/app.jar /app.jar
CMD ["java", "-jar", "/app.jar"]镜像大小:471MB 😱
第二版:知道用 slim 了(初级玩家)
FROM openjdk:17-slim
WORKDIR /app
COPY target/app.jar app.jar
EXPOSE 8080
CMD ["java", "-jar", "app.jar"]镜像大小:291MB 🤔
第三版:多阶段构建(高级玩家)
# ===== 构建阶段 =====
FROM maven:3.9-eclipse-temurin-17 AS builder
WORKDIR /build
COPY pom.xml .
# 先下载依赖(利用缓存层)
RUN mvn dependency:go-offline -B
COPY src ./src
RUN mvn clean package -DskipTests -B
# ===== 运行阶段 =====
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
# 安全:创建非 root 用户
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
COPY --from=builder /build/target/*.jar app.jar
# 安全:切换到非 root 用户
USER appuser
EXPOSE 8080
# JVM 调优参数
ENTRYPOINT ["java", \
"-XX:+UseContainerSupport", \
"-XX:MaxRAMPercentage=75.0", \
"-XX:+UseG1GC", \
"-Djava.security.egd=file:/dev/./urandom", \
"-jar", "app.jar"]镜像大小:148MB 🎉
进化过程:
471MB
🐘"] -->|"用 slim"| V2["第二版
291MB
🐕"] V2 -->|"多阶段构建
+ Alpine
+ JRE only"| V3["第三版
148MB
🐁"] V3 -->|"GraalVM
Native Image"| V4["终极版
~50MB
🐜"] style V1 fill:#ffcdd2 style V2 fill:#fff9c4 style V3 fill:#c8e6c9 style V4 fill:#b3e5fc
4.3 Dockerfile 最佳实践(血泪总结)
# ❌ 错误示范:每个命令一个 RUN(层数爆炸)
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y wget
RUN apt-get install -y vim
RUN rm -rf /var/lib/apt/lists/*
# ✅ 正确姿势:合并 RUN,减少镜像层数
RUN apt-get update && \
apt-get install -y --no-install-recommends \
curl \
wget \
&& rm -rf /var/lib/apt/lists/*
# 注意:vim 不要装!容器里 debug 用 docker exec!
# ❌ 错误示范:COPY 放在依赖安装前面(缓存全废)
COPY . /app
RUN npm install
# ✅ 正确姿势:先 COPY 依赖文件,再 COPY 源码
COPY package.json package-lock.json ./
RUN npm ci --only=production
COPY . .🧠 缓存原理:Dockerfile 每一条指令都是一层。如果某一层没变化,Docker 会用缓存。所以把不常变的放前面,常变的放后面。
五、Docker Compose——真正的生产力工具
5.1 为什么需要 Docker Compose?
因为一个真实项目长这样:
反向代理"] NGINX --> APP1["App 实例 1
Spring Boot"] NGINX --> APP2["App 实例 2
Spring Boot"] APP1 --> MYSQL["MySQL 8.0
主数据库"] APP2 --> MYSQL APP1 --> REDIS["Redis 7
缓存"] APP2 --> REDIS APP1 --> ES["Elasticsearch
搜索引擎"] APP2 --> ES APP1 --> RABBIT["RabbitMQ
消息队列"] APP2 --> RABBIT style USER fill:#e1f5fe style NGINX fill:#fff9c4 style APP1 fill:#c8e6c9 style APP2 fill:#c8e6c9 style MYSQL fill:#ffcdd2 style REDIS fill:#ffcdd2 style ES fill:#ffcdd2 style RABBIT fill:#ffcdd2
你打算手动 docker run 7 个容器?还要配网络、卷、依赖顺序?
Docker Compose:一个 YAML 文件搞定一切。
5.2 完整的 docker-compose.yml 示例
version: '3.8'
services:
# ============ Nginx 反向代理 ============
nginx:
image: nginx:1.25-alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- ./nginx/ssl:/etc/nginx/ssl:ro
depends_on:
app:
condition: service_healthy
restart: unless-stopped
networks:
- frontend
# ============ Spring Boot 应用 ============
app:
build:
context: .
dockerfile: Dockerfile
environment:
- SPRING_PROFILES_ACTIVE=prod
- DB_HOST=mysql
- DB_PORT=3306
- REDIS_HOST=redis
- TZ=Asia/Dubai
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_healthy
deploy:
replicas: 2 # 跑两个实例
resources:
limits:
memory: 512M
cpus: '0.5'
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
restart: unless-stopped
networks:
- frontend
- backend
# ============ MySQL ============
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} # 从 .env 文件读取
MYSQL_DATABASE: payment_db
MYSQL_USER: app_user
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
volumes:
- mysql_data:/var/lib/mysql
- ./sql/init.sql:/docker-entrypoint-initdb.d/init.sql:ro
ports:
- "3306:3306" # 生产环境建议去掉,只让内部访问
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
networks:
- backend
# ============ Redis ============
redis:
image: redis:7-alpine
command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD}
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
networks:
- backend
# ============ 持久化卷 ============
volumes:
mysql_data:
driver: local
redis_data:
driver: local
# ============ 网络 ============
networks:
frontend:
driver: bridge
backend:
driver: bridge5.3 Docker Compose 常用命令
# 启动所有服务(后台)
docker compose up -d
# 查看服务状态
docker compose ps
# 查看日志
docker compose logs -f app
# 重新构建并启动
docker compose up -d --build
# 扩缩容
docker compose up -d --scale app=3
# 停止所有服务
docker compose down
# 停止并删除所有数据(危险!)
docker compose down -v # -v 会删除 volumes!六、Docker 网络——开始头疼了
6.1 Docker 网络模式
172.17.0.1"] C1_B["容器A
172.17.0.2"] C2_B["容器B
172.17.0.3"] BR --- C1_B BR --- C2_B end subgraph "Host 模式" HOST["宿主机网络
直接使用宿主机端口"] C1_H["容器
共享宿主机网络栈"] HOST --- C1_H end subgraph "None 模式" C1_N["容器
没有网络 🚫
与世隔绝"] end subgraph "自定义 Bridge" CBR["my-network
172.20.0.1"] C1_C["app
172.20.0.2"] C2_C["mysql
172.20.0.3"] CBR --- C1_C CBR --- C2_C C1_C -.->|"可通过容器名访问
mysql:3306 ✅"| C2_C end
🔑 关键知识点:默认 bridge 网络中,容器之间不能通过容器名互相访问。自定义网络中可以。这就是为什么 Docker Compose 默认会创建一个自定义网络。
这个坑大概会浪费你 2-4 小时。不客气。
6.2 常见网络问题决策树
宿主机吗?"} Q1 -->|"不能"| A1["检查 Docker 网络模式
docker network inspect"] Q1 -->|"能"| Q2{"容器之间能
互相通信吗?"} Q2 -->|"不能"| Q3{"它们在同一个
网络中吗?"} Q3 -->|"不是"| A2["把它们加到同一个网络
docker network connect"] Q3 -->|"是"| A3["检查防火墙规则
iptables -L"] Q2 -->|"能"| Q4{"外部能访问
容器服务吗?"} Q4 -->|"不能"| Q5{"端口映射
配了吗?"} Q5 -->|"没有"| A4["加上 -p 参数"] Q5 -->|"配了"| A5["检查宿主机防火墙
和云服务器安全组 ☁️"] Q4 -->|"能"| A6["那你来找我干啥?🤷"]
七、Docker 数据管理——丢数据的噩梦
7.1 三种数据持久化方式
/home/joey/data"] B_CT["容器内: /data"] B_HOST -.-> B_CT end subgraph "方式三:tmpfs(临时数据 ⚡)" T_MEM["宿主机内存"] T_CT["容器内: /tmp"] T_MEM -.-> T_CT end
# Volume(Docker 管理,最推荐)
docker run -v mysql_data:/var/lib/mysql mysql:8.0
# Bind Mount(宿主机路径直接挂载)
docker run -v /home/joey/config:/app/config:ro nginx
# tmpfs(存内存里,重启就没了)
docker run --tmpfs /tmp:rw,noexec,nosuid,size=100m app⚠️ 血泪教训:如果你没有配置持久化,容器一删,数据全没。MySQL 的数据、Redis 的持久化、上传的文件——统统灰飞烟灭。
这不是 bug,这是 feature。Docker 的哲学是:容器是牲畜(Cattle),不是宠物(Pet)。 随时可以杀掉重建。但你的数据不是。
八、Docker 镜像优化——从 1GB 到 50MB 的艺术
8.1 镜像大小对比
77MB"] D["debian:12-slim
74MB"] A["alpine:3.19
7MB"] DL["distroless
~2MB"] S["scratch
0MB"] end U -.->|"换成 Debian"| D D -.->|"换成 Alpine"| A A -.->|"换成 Distroless"| DL DL -.->|"终极:从零开始"| S style U fill:#ffcdd2 style D fill:#fff9c4 style A fill:#c8e6c9 style DL fill:#b3e5fc style S fill:#e1bee7
8.2 镜像优化 Checklist
alpine / distroless / slim"] S1 --> S2["✅ 2. 多阶段构建
build 和 runtime 分离"] S2 --> S3["✅ 3. 合并 RUN 指令
减少镜像层数"] S3 --> S4["✅ 4. 清理缓存和临时文件
apt clean / rm -rf /tmp/*"] S4 --> S5["✅ 5. 使用 .dockerignore
排除 .git, node_modules 等"] S5 --> S6["✅ 6. 固定版本号
别用 latest 标签"] S6 --> S7["✅ 7. 非 root 用户运行
USER appuser"] S7 --> DONE["🎉 镜像小且安全"]
.dockerignore 示例:
.git
.gitignore
node_modules
npm-debug.log
Dockerfile
docker-compose*.yml
.env
*.md
.idea
.vscode
target/九、Docker 在生产中的坑——开始想放弃了
9.1 日志管理
# 你以为你的磁盘满了是因为数据多?
# 不,是因为 Docker 日志。
# 查看容器日志文件大小
du -sh /var/lib/docker/containers/*/*-json.log
# 全局配置日志大小限制(/etc/docker/daemon.json)
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
# 单个容器配置
docker run --log-opt max-size=10m --log-opt max-file=3 nginx😱 真实案例:某服务器磁盘 100% 了,查了半天发现是一个容器的日志文件吃掉了 47GB。里面全是 NullPointerException 的堆栈信息,每秒打印 200 条。9.2 容器健康检查
等待应用启动 loop 每隔 interval (10s) D->>H: 执行健康检查命令 H->>C: curl http://localhost:8080/health alt 响应 200 C-->>H: ✅ Healthy H-->>D: 状态: healthy else 超时或非200 C-->>H: ❌ Failed H-->>D: 失败计数 +1 Note over D: retries 次失败后
标记为 unhealthy end end
9.3 生产环境安全清单
USER nonroot"] SEC --> S2["只读文件系统
--read-only"] SEC --> S3["限制资源
--memory --cpus"] SEC --> S4["不用 --privileged
除非你知道在干什么"] SEC --> S5["定期更新基础镜像
修复安全漏洞"] SEC --> S6["扫描镜像漏洞
docker scout / trivy"] SEC --> S7["使用 secrets 管理敏感信息
别把密码写在镜像里!"] style SEC fill:#ffcdd2
十、Docker 常见问题排查——放弃前的最后挣扎
10.1 遇到问题的标准流程
容器还活着吗?"] STEP1 --> STEP2["2. docker logs 容器名
看看日志说了啥"] STEP2 --> STEP3["3. docker inspect 容器名
配置对吗?"] STEP3 --> STEP4["4. docker exec -it 容器名 sh
进去看看"] STEP4 --> STEP5["5. docker stats
资源够吗?"] STEP5 --> STEP6{"解决了吗?"} STEP6 -->|"是"| HAPPY["🎉"] STEP6 -->|"否"| STEP7["6. docker rm -f && docker run
删了重来"] STEP7 --> STEP8{"解决了吗?"} STEP8 -->|"是"| HAPPY STEP8 -->|"否"| STEP9["7. docker system prune -a
全部清理重来"] STEP9 --> STEP10{"解决了吗?"} STEP10 -->|"是"| HAPPY STEP10 -->|"否"| GIVEUP["8. 重装系统 💀
(从入门到放弃)"]
10.2 经典错误合集
| 错误信息 | 原因 | 解决方案 |
|---|---|---|
port is already allocated | 端口被占用 | lsof -i :端口号 找到并 kill |
no space left on device | 磁盘满了 | docker system prune -a |
OCI runtime error | 容器启动失败 | 检查 CMD/ENTRYPOINT 命令 |
network not found | 网络不存在 | docker network create xxx |
permission denied | 权限问题 | 检查文件权限 / 用户 / SELinux |
exec format error | 架构不匹配 (amd64/arm64) | 用 --platform 指定 或 buildx |
name already in use | 容器名冲突 | docker rm 旧容器 |
十一、高级话题——如果你还没放弃的话
11.1 Docker 底层原理(简化版)
进程隔离"] NS2["Network Namespace
网络隔离"] NS3["Mount Namespace
文件系统隔离"] NS4["UTS Namespace
主机名隔离"] NS5["IPC Namespace
进程间通信隔离"] NS6["User Namespace
用户隔离"] end subgraph "Cgroups(限制)" CG1["CPU 限制"] CG2["内存限制"] CG3["IO 限制"] CG4["网络带宽限制"] end subgraph "UnionFS(分层)" L1["Layer 1: Base Image
(alpine)"] L2["Layer 2: apt install
(只读)"] L3["Layer 3: COPY code
(只读)"] L4["Layer 4: 容器层
(可写)"] L1 --> L2 --> L3 --> L4 end end
用人话翻译:
- Namespace:让每个容器觉得自己是独生子,拥有独立的世界
- Cgroups:给每个容器分配零花钱(资源),不许超支
- UnionFS:千层饼式的文件系统,一层叠一层,最上面那层才能写
11.2 Docker vs Podman vs Containerd
(dockerd)
⚠️ 需要 root"] DD --> CTRD1["containerd"] CTRD1 --> RUNC1["runc"] RUNC1 --> C1["容器"] end subgraph "Podman 架构" PC["Podman CLI"] --> CTRD2["conmon"] CTRD2 --> RUNC2["runc"] RUNC2 --> C2["容器"] Note1["✅ 无守护进程
✅ 无需 root
✅ 兼容 Docker CLI"] end subgraph "Containerd(K8s 默认)" KC["crictl / nerdctl"] --> CTRD3["containerd"] CTRD3 --> RUNC3["runc"] RUNC3 --> C3["容器"] Note2["✅ K8s 原生支持
✅ 更轻量"] end
🤔 那该用哪个?
- 个人开发/学习 → Docker(生态最完善)
- 安全敏感场景 → Podman(rootless)
- K8s 集群 → Containerd(K8s 默认运行时)
- 选择困难症 → Docker(别想了,先用起来)
十二、Docker 知识体系总览
结语:从入门到放弃,然后再爬起来
Docker 的学习曲线长这样:
一行命令启动 MySQL"] --> B["😊 Dockerfile 也不难嘛"] B --> C["🤔 Docker Compose 有点东西"] C --> D["😰 网络怎么不通?"] D --> E["😱 数据丢了!"] E --> F["💀 磁盘满了,日志 47GB"] F --> G["🪦 想放弃了..."] G --> H["😤 不行,我要搞懂它"] H --> I["🧠 原来如此!"] I --> J["🏆 真正掌握 Docker"] J --> K["😭 老板说要学 K8s 了..."]
放弃不是终点,K8s 才是。
📝 关于本文
这是「从入门到放弃」系列的第 N 篇。如果你觉得有用,欢迎访问 underestimated.cn 查看更多技术分享。
下一篇预告:《Kubernetes 从入门到放弃》——如果说 Docker 是一个容器,那 K8s 就是管理一群容器的噩梦...呃,我是说编排系统。
最后的最后,送你一条保命命令:
# 当一切都无法挽回的时候
alias yolo='docker system prune -a --volumes -f && echo "🔥 Everything is gone. Start fresh."'