# Docker 实战指南 — 从"看懂"到"会用" > 写给 Deshill,2026-08-02 > 前提:你已经掌握了镜像/容器/仓库三个概念和基础命令 --- ## Part 0:先把心态调对 你现在的状态是**"看懂了语法,不知道写什么程序"**。 这非常正常。Docker 的学习曲线是这样的: ``` 阶段1:知道命令 ← 你在这里 阶段2:能跑别人的镜像(nginx、mysql、redis) 阶段3:能把自己的项目打包成镜像 阶段4:能用 docker-compose 编排多个服务 阶段5:知道什么时候不该用 Docker ← 最高境界 ``` 这份文档带你走完 2→4,并在最后回答"什么时候用 Docker,什么时候用 apt"。 --- # Part 1:什么时候用 Docker,什么时候用 apt install 这是你问的第二个问题,但其实应该先答,因为它决定了你要不要往下学。 ## 一句话判断法 > **apt install** = 装在"你的房子"里 > **docker run** = 给它盖一间"独立小屋",用完可以整栋拆掉 ## 决策表 | 场景 | 用什么 | 为什么 | |---|---|---| | `git`、`vim`、`curl`、`htop` 等命令行工具 | ✅ **apt** | 你要直接用的工具,装容器里反而累 | | `gcc`、`make`、`python3` 基础环境 | ✅ **apt** | 系统级基础设施 | | **MySQL / PostgreSQL / Redis / MongoDB** | ✅ **Docker** | 版本切换方便、卸载干净、不污染系统 | | **Nginx(做反向代理)** | 🤔 **都行** | 只做反代用 apt 更简单;配合容器编排用 Docker | | **你自己写的 Web 项目** | ✅ **Docker** | 部署可复现,换服务器直接跑 | | **需要特定 Python 版本的项目** | ✅ **Docker** | 比 conda/venv 更彻底 | | **AI/深度学习环境(CUDA、PyTorch)** | ✅ **Docker** | 环境地狱的唯一解药 | | **一次性试用某个软件** | ✅ **Docker** | 试完 `docker rm` 什么都不留 | | **需要访问硬件(GPIO、串口、摄像头)** | 🤔 **apt 优先** | 容器访问硬件要额外配置 | | **系统服务(ssh、防火墙、cron)** | ✅ **apt** | 这些本来就该在宿主机 | | **单文件的小脚本** | ✅ **直接跑** | 别过度工程化 | ## 三个具体的判断问题 遇到"该用哪个"时,问自己: ### 问题 1:这东西会不会污染系统? **会污染的典型**: - MySQL 装 apt → 装了一堆依赖、systemd 服务、配置文件散落各处 - 想升级到另一个版本 → 卸载不干净、残留配置、报奇怪的错 → **这种用 Docker** **不会污染的**: - `git`、`curl`、`jq` 这种单一二进制工具 → **这种用 apt** ### 问题 2:我需要多个版本共存吗? - 项目 A 要 Python 3.8,项目 B 要 Python 3.12 → **Docker** - 只需要一个 Python → **apt** ### 问题 3:这东西以后要部署到别的机器吗? - 是 → **Docker**(写好 Dockerfile,到哪都能跑) - 否,只是本机用 → **apt** ## 一个反面教材 ❌ **不要这样做**: ```bash # 用 Docker 跑一个 grep docker run --rm -v $(pwd):/data alpine grep "hello" /data/file.txt ``` 这是**过度工程化**。`grep` 就该直接用。 ✅ **判断原则**: > **Docker 是为"复杂依赖 + 需要隔离 + 需要复现"设计的。** > 简单工具直接 apt,别为了用 Docker 而用 Docker。 --- # Part 2:把你的项目变成 Docker 镜像 这是你问的第一个问题。我用**三个真实项目**教你,从简单到复杂。 ## 核心概念:Dockerfile **Dockerfile 就是"打包说明书"** —— 告诉 Docker 怎么造出你的镜像。 ``` Dockerfile(说明书) ↓ docker build 镜像(安装包) ↓ docker run 容器(运行中的程序) ``` --- ## 案例 1:打包一个 Python Flask 项目(最基础) ### 项目结构 ``` my-app/ ├── app.py ├── requirements.txt └── Dockerfile ``` ### app.py ```python from flask import Flask app = Flask(__name__) @app.route("/") def hello(): return "Hello from Docker!" if __name__ == "__main__": app.run(host="0.0.0.0", port=5000) ``` **⚠️ 关键点**:`host="0.0.0.0"` 而不是 `127.0.0.1` > 容器内的 `127.0.0.1` 只有容器自己能访问,外面进不来。必须监听 `0.0.0.0`。 > **这是新手 90% 的踩坑点。** ### requirements.txt ``` flask==3.0.0 ``` ### Dockerfile ```dockerfile # 1. 基础镜像:选一个已经装好 Python 的环境 FROM python:3.12-slim # 2. 设置容器内的工作目录 WORKDIR /app # 3. 先只复制依赖文件(利用缓存,见下方说明) COPY requirements.txt . # 4. 安装依赖 RUN pip install --no-cache-dir -r requirements.txt # 5. 再复制代码 COPY . . # 6. 声明端口(仅文档作用,不实际开放) EXPOSE 5000 # 7. 容器启动时执行的命令 CMD ["python", "app.py"] ``` ### 构建和运行 ```bash # 构建镜像(-t 是给镜像起名字,最后的 . 是 Dockerfile 所在目录) docker build -t my-app:v1 . # 运行容器 docker run -d -p 8080:5000 --name my-app-run my-app:v1 # 访问 http://localhost:8080 curl http://localhost:8080 # 看日志 docker logs -f my-app-run ``` ### 💡 为什么要"先 COPY requirements.txt,后 COPY 代码"? **Docker 的分层缓存机制**: Dockerfile 每一行都生成一个"层"。**只有改动的那一层及其之后的层需要重建。** ``` 如果这样写: COPY . . ← 改一行代码,这层就变了 RUN pip install -r requirements.txt ← 依赖也得重装(慢!) 正确写法: COPY requirements.txt . ← 依赖没变,用缓存 RUN pip install -r requirements.txt ← 用缓存,秒过 COPY . . ← 只有这层重建 ``` **效果**:改代码后重新 build,从 2 分钟变成 3 秒。 --- ## 案例 2:打包一个 Node.js / 前端项目(多阶段构建) ### 问题 前端项目需要 `node` + `npm` 来编译,但**编译完只需要静态文件**。 如果直接打包,镜像会有 1GB+(因为包含 node_modules 和整个 Node 环境)。 ### 解决:多阶段构建(Multi-stage Build) ```dockerfile # ===== 阶段 1:构建 ===== FROM node:20-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build # 产出 dist/ 目录 # ===== 阶段 2:运行(只要结果,不要工具)===== FROM nginx:alpine # 只把上一阶段的产物复制过来 COPY --from=builder /app/dist /usr/share/nginx/html COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80 CMD ["nginx", "-g", "daemon off;"] ``` **效果**:镜像从 **1.2GB → 25MB** 🎯 **这个技巧同样适用于**: - Go 项目(编译需要 Go 工具链,运行只需要二进制) - Rust 项目(同理) - Java 项目(编译要 JDK,运行只要 JRE) --- ## 案例 3:打包你的 hr-assistant RAG 项目(贴近你的实际) 你之前那个 RAG 项目用了 Gradio + LLM API,正好当例子。 ### 项目结构 ``` hr-assistant/ ├── src/ │ ├── app.py │ └── RAG/ ├── requirements.txt ├── .env ← 密钥,绝对不要打进镜像! ├── .dockerignore └── Dockerfile ``` ### .dockerignore(重要!) ``` .env .git __pycache__ *.pyc .venv venv/ data/vectorstore/ *.log .DS_Store ``` > **`.dockerignore` 的作用等同于 `.gitignore`** > 没有它,你的 `.env`(含 API Key)会被打进镜像 → **泄露风险** ### Dockerfile ```dockerfile FROM python:3.11-slim # 装系统依赖(有些 Python 包需要编译) RUN apt-get update && apt-get install -y --no-install-recommends \ gcc g++ \ && rm -rf /var/lib/apt/lists/* WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY src/ ./src/ # Gradio 默认端口 EXPOSE 7860 # 让 Gradio 监听所有网卡 ENV GRADIO_SERVER_NAME=0.0.0.0 CMD ["python", "src/app.py"] ``` ### 运行(密钥通过环境变量注入,不打进镜像) ```bash docker build -t hr-assistant:v1 . docker run -d \ -p 7860:7860 \ --name hr-rag \ --env-file .env \ -v $(pwd)/data:/app/data \ hr-assistant:v1 ``` **两个关键参数**: - `--env-file .env` → **运行时**注入密钥(镜像里没有密钥) - `-v $(pwd)/data:/app/data` → **挂载数据卷**(容器删了数据还在) --- # Part 3:必须掌握的三个进阶概念 ## 概念 1:数据卷(Volume)—— 数据持久化 **核心问题**:容器删了,里面的数据全没了。 ```bash # ❌ 错误:MySQL 数据存在容器里 docker run -d --name mysql1 mysql:8 # docker rm mysql1 → 数据库全没了 💀 # ✅ 正确:把数据存在宿主机 docker run -d \ --name mysql1 \ -v /opt/mysql-data:/var/lib/mysql \ -e MYSQL_ROOT_PASSWORD=xxx \ mysql:8 # 容器删了,/opt/mysql-data 还在 ✅ ``` ### 两种挂载方式 | 写法 | 叫法 | 用途 | |---|---|---| | `-v /宿主机路径:/容器路径` | **绑定挂载** | 你要直接看/改文件(配置、代码、数据)| | `-v 卷名:/容器路径` | **命名卷** | 交给 Docker 管理(数据库数据)| ```bash # 绑定挂载:开发时热更新代码 docker run -v $(pwd)/src:/app/src my-app # 命名卷:让 Docker 管 docker volume create mydata docker run -v mydata:/var/lib/mysql mysql:8 docker volume ls # 查看所有卷 docker volume inspect mydata ``` --- ## 概念 2:网络(Network)—— 容器之间怎么通信 **核心问题**:Web 容器怎么连数据库容器? ### ❌ 错误做法 ```python # Web 容器里这样写 db_host = "localhost" # ← 找不到!localhost 是容器自己 db_host = "127.0.0.1" # ← 同样找不到 ``` ### ✅ 正确做法:自定义网络 + 容器名当域名 ```bash # 1. 创建网络 docker network create my-net # 2. 数据库加入网络 docker run -d --name mydb --network my-net \ -e MYSQL_ROOT_PASSWORD=xxx mysql:8 # 3. Web 应用加入同一网络 docker run -d --name myweb --network my-net -p 8080:5000 my-app ``` **然后在 Web 代码里**: ```python db_host = "mydb" # ← 直接用容器名!Docker 内置 DNS 会解析 ``` **这是 Docker 最优雅的设计之一**:同一网络内,容器名 = 域名。 --- ## 概念 3:docker-compose —— 一键管理多个容器 **核心问题**:上面那个例子要敲 3 条命令,还得记住顺序和参数。项目一复杂就崩了。 ### 解决:写成一个 `docker-compose.yml` ```yaml services: # 数据库 db: image: mysql:8 container_name: myapp-db restart: unless-stopped environment: MYSQL_ROOT_PASSWORD: ${DB_PASSWORD} MYSQL_DATABASE: myapp volumes: - db-data:/var/lib/mysql healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] interval: 10s retries: 5 # Redis 缓存 redis: image: redis:7-alpine restart: unless-stopped # 你的应用 web: build: . # 用当前目录的 Dockerfile 构建 container_name: myapp-web restart: unless-stopped ports: - "8080:5000" environment: DB_HOST: db # 直接用服务名 REDIS_HOST: redis env_file: - .env depends_on: db: condition: service_healthy # 等数据库真的准备好 volumes: - ./src:/app/src # 开发时热更新 volumes: db-data: ``` ### 命令(就这几条,全部够用) ```bash docker compose up -d # 启动全部(后台) docker compose up -d --build # 重新构建并启动 docker compose down # 停止并删除容器 docker compose down -v # 连数据卷一起删(⚠️ 数据没了) docker compose ps # 查看状态 docker compose logs -f web # 看某个服务的日志 docker compose restart web # 重启某个服务 docker compose exec web bash # 进入某个容器 ``` **从此以后,部署一个项目 = `git clone` + `docker compose up -d`** 🎯 --- # Part 4:给你的具体学习路径 ## 第 1 周:跑别人的镜像(建立手感) ```bash # 1. 跑个 nginx,改改首页 docker run -d -p 8080:80 --name web nginx docker exec -it web bash # 进去后:echo "Hello" > /usr/share/nginx/html/index.html # 2. 跑个 MySQL,用客户端连上 docker run -d --name mydb \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=123456 \ -v /opt/mysql-data:/var/lib/mysql \ mysql:8 # 3. 跑个 Redis docker run -d --name cache -p 6379:6379 redis:7-alpine # 4. 练习:停止、启动、删除、重建,观察数据在不在 ``` **目标**:习惯 `-d -p -v -e --name` 这五个参数。 ## 第 2 周:打包自己的项目 **推荐从你的 `word-learning-app` 开始**(比 RAG 简单): 1. 写 `Dockerfile` 2. 写 `.dockerignore` 3. `docker build -t word-app:v1 .` 4. `docker run -d -p 3456:3456 word-app:v1` 5. 浏览器能打开 = 成功 🎉 **遇到问题的排查顺序**: ```bash docker logs 容器名 # 90% 的问题看日志就知道 docker exec -it 容器名 bash # 进去看看文件在不在 docker inspect 容器名 # 看网络、挂载配置 ``` ## 第 3 周:docker-compose 编排 把 `word-learning-app` 改成: - `web`:你的 Node/Python 应用 - `db`:MySQL 或 PostgreSQL - 用 compose 一键起 ## 第 4 周:真实部署 把项目部署到你的**腾讯云服务器**(129.211.3.54): ```bash # 服务器上 git clone 你的仓库 cd 项目 docker compose up -d ``` **然后配 Nginx 反代 + HTTPS** → 你就有了一个真实的线上服务。 --- # Part 5:你项目里的实战场景(对号入座) | 你的项目 | 用 Docker 的价值 | 优先级 | |---|---|---| | **hr-assistant(RAG)** | 依赖多、环境复杂 → **最该 Docker 化** | ⭐⭐⭐ | | **word-learning-app** | 简单,但适合当练手 | ⭐⭐⭐(练手首选)| | **考研数学题目生成** | 单脚本,Docker 化收益低 | ⭐ | | **未来的机器人视觉(ROS2)** | 官方就推荐 Docker,环境地狱 | ⭐⭐⭐ | | **Frigate 家庭监控** | 官方只提供 Docker 部署 | ⭐⭐⭐(已经在用了)| | **未来的 DPO/RLHF 训练** | CUDA + PyTorch 版本地狱 → **必须 Docker** | ⭐⭐⭐ | ## 特别提示:AI 训练环境 你暑假要学 DPO/GRPO/PPO,**一定会遇到**: ``` CUDA 12.1 + PyTorch 2.1 + transformers 4.36 + trl 0.7.x ``` 这套组合装错一个版本就报一堆莫名的错。 **用官方镜像可以直接跳过所有环境问题**: ```bash docker run --gpus all -it \ -v $(pwd):/workspace \ pytorch/pytorch:2.1.0-cuda12.1-cudnn8-devel \ bash ``` **这就是 Docker 对你最大的价值。** 🎯 --- # Part 6:常见坑(提前避雷) | 坑 | 现象 | 解决 | |---|---|---| | **监听 127.0.0.1** | 容器起来了但访问不了 | 改成 `0.0.0.0` | | **端口写反** | `-p 5000:8080` 访问不通 | 记住:`-p 宿主机:容器` | | **密钥打进镜像** | 安全事故 | 用 `--env-file` + `.dockerignore` | | **忘挂载数据卷** | 容器删了数据没了 | 数据库一定要 `-v` | | **镜像体积巨大** | 传输慢、占空间 | 用 `-slim`/`-alpine` + 多阶段构建 | | **改代码不生效** | 改完还是老的 | 要 `docker build` 重建,或开发时挂载源码 | | **容器名冲突** | `name already in use` | `docker rm 旧容器` 或换名字 | | **磁盘被占满** | `no space left` | `docker system prune -a`(清理无用镜像)| | **时区不对** | 日志时间差 8 小时 | `-v /etc/localtime:/etc/localtime:ro` | | **容器一起来就退出** | `docker ps` 看不到 | 前台进程结束了;`docker logs` 看原因 | ## 一条救命命令 ```bash # 磁盘满了 / 想大扫除 docker system df # 先看占用情况 docker system prune -a # 清理未使用的镜像、容器、网络 docker volume prune # ⚠️ 清理未使用的卷(小心,会删数据) ``` --- # Part 7:一页速查表 ## 镜像 ```bash docker build -t 名字:标签 . # 构建 docker images # 列出 docker rmi 名字:标签 # 删除 docker pull 名字:标签 # 拉取 docker tag 旧名 新名 # 打标签 ``` ## 容器 ```bash docker run -d -p 外:内 --name 名 -v 宿主:容器 -e KEY=V 镜像 docker ps / docker ps -a # 查看 docker logs -f 名 # 日志(最常用!) docker exec -it 名 bash # 进入 docker stop/start/restart 名 # 生命周期 docker rm -f 名 # 删除 docker stats # 资源监控 docker cp 名:/路径 ./本地 # 拷文件出来 ``` ## Compose ```bash docker compose up -d --build docker compose down docker compose logs -f 服务名 docker compose exec 服务名 bash docker compose ps ``` ## run 常用参数(背下来) ``` -d 后台运行 -p 8080:80 端口映射(宿主机:容器) -v /a:/b 挂载(宿主机:容器) -e KEY=VALUE 环境变量 --env-file .env 从文件读环境变量 --name xxx 容器名 --network xxx 加入网络 --restart unless-stopped 自动重启 --rm 退出后自动删除(一次性任务) -it 交互式终端 --gpus all 使用 GPU ``` --- # 总结:三句话 1. **什么时候用 Docker?** > 依赖复杂、需要隔离、需要在别的机器复现 → Docker > 简单命令行工具、系统服务 → apt 2. **怎么把项目 Docker 化?** > 写 `Dockerfile`(选基础镜像 → 装依赖 → 拷代码 → 定启动命令) > 加 `.dockerignore` > 多服务就上 `docker-compose.yml` 3. **下一步做什么?** > 拿 `word-learning-app` 练手,一周内把它 Docker 化并跑起来。 --- ## 你的第一个作业 ```bash cd /root/.openclaw/workspace/word-learning-app # 1. 写 Dockerfile # 2. 写 .dockerignore # 3. docker build -t word-app:v1 . # 4. docker run -d -p 3456:3456 --name word-app word-app:v1 # 5. curl http://localhost:3456 ``` **卡住了随时问我,我给你调。** 🍃