Docker 实战教学
Docker 实战教学
前置要求:已阅读基础知识篇,Docker Desktop 已安装并运行。
本文包含三个由浅入深的 Python 实战项目,每个项目都是完整可运行的示例。
- 完整代码项目地址:gitee
项目一:Flask Web API
目标: 用 Docker 部署一个 Python Web 服务,通过浏览器或接口工具访问它。
学到的技能: 构建镜像、端口映射、查看日志。
1.1 项目文件结构
在你的电脑上新建一个文件夹 flask-demo,创建以下三个文件:
flask-demo/
├── app.py
├── requirements.txt
└── Dockerfile
1.2 编写代码
app.py
# 项目代码文件中有详细注释
from flask import Flask, jsonify
app = Flask(__name__)
@app.route("/")
def hello():
return jsonify({"message": "Hello from Docker!", "status": "ok"})
@app.route("/greet/<name>")
def greet(name):
return jsonify({"message": f"Hello, {name}!"})
if __name__ == "__main__":
# 监听 0.0.0.0 才能被容器外部访问,不要用 127.0.0.1
app.run(host="0.0.0.0", port=5000)
requirements.txt
flask==3.1.3
Dockerfile
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
EXPOSE 5000
CMD ["python", "app.py"]
- 关于为什么不直接在前面
COPY . .,而是单独COPY requirements.txt .,这是有说法的,与docker的缓存优化有关,详细可以问AI。
1.3 构建并运行
打开终端,进入 flask-demo 目录:
cd flask-demo
# 构建镜像,-t 指定名称
docker build -t flask-demo:1.0 .
构建过程会看到每一步的输出,第一次构建需要下载基础镜像,之后会有缓存,速度很快。
- 这里提一嘴,docker和git一样有
.dockerignore文件,可以忽略一些文件,不参与构建镜像。
# 运行容器
# -d 后台运行
# -p 8080:5000 把本机 8080 端口映射到容器内的 5000 端口
# --name flask-app 给容器起名字
docker run -d -p 8080:5000 --name flask-app flask-demo:1.0
# 启动时使用终端
docker run -it -p 8080:5000 --name flask-app flask-demo:1.0 /bin/bash
1.4 验证结果
打开浏览器访问:
http://localhost:8080— 应该看到{"message": "Hello from Docker!", "status": "ok"}http://localhost:8080/greet/张三— 应该看到{"message": "Hello, 张三!"}
查看日志:
docker logs flask-app
1.5 停止和清理
docker stop flask-app
docker rm flask-app
项目二:Python + SQLite 数据存储
目标: 将数据保存到数据库文件,并通过数据卷让数据在容器重启后不丢失。
学到的技能: 数据卷挂载(-v)、数据持久化、环境变量传入。
2.1 项目文件结构
新建文件夹 sqlite-demo:
sqlite-demo/
├── app.py
├── requirements.txt
└── Dockerfile
同时在 sqlite-demo 旁边创建一个 data 文件夹用于存放数据库文件:
sqlite-demo/
data/ ← 这个文件夹用来持久化数据库
2.2 编写代码
app.py
import sqlite3
import os
from flask import Flask, jsonify, request
app = Flask(__name__)
# 数据库路径,从环境变量读取,默认放在 /data 目录下
DB_PATH = os.environ.get("DB_PATH", "/data/notes.db")
def get_db():
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row
return conn
def init_db():
os.makedirs(os.path.dirname(DB_PATH), exist_ok=True)
conn = get_db()
conn.execute(
"CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, content TEXT)"
)
conn.commit()
conn.close()
@app.route("/notes", methods=["GET"])
def list_notes():
conn = get_db()
notes = conn.execute("SELECT * FROM notes").fetchall()
conn.close()
return jsonify([dict(n) for n in notes])
@app.route("/notes", methods=["POST"])
def add_note():
content = request.json.get("content", "")
conn = get_db()
conn.execute("INSERT INTO notes (content) VALUES (?)", (content,))
conn.commit()
conn.close()
return jsonify({"result": "added", "content": content}), 201
if __name__ == "__main__":
init_db()
app.run(host="0.0.0.0", port=5000)
requirements.txt
flask==3.0.3
Dockerfile
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
# 创建数据目录
RUN mkdir -p /data
EXPOSE 5000
CMD ["python", "app.py"]
2.3 构建并运行
cd sqlite-demo
docker build -t sqlite-demo:1.0 .
运行时用 -v 挂载本地 data 目录(注意路径要用绝对路径):
# Windows 用户:把 /c/Users/你的用户名/... 替换为实际路径
# 下面的写法适用于 Git Bash / PowerShell
# Git Bash 写法:
docker run -d \
-p 8080:5000 \
-v "$(pwd)/../data:/data" \
--name sqlite-app \
sqlite-demo:1.0
# PowerShell 写法:
docker run -d -p 8080:5000 -v "${PWD}\..\data:/data" --name sqlite-app sqlite-demo:1.0
2.4 验证数据持久化
添加一条笔记:
用 PowerShell:
Invoke-WebRequest -Uri "http://localhost:8080/notes" `
-Method POST `
-ContentType "application/json" `
-Body '{"content": "我的第一条笔记"}'
或者安装 curl 后:
curl -X POST http://localhost:8080/notes \
-H "Content-Type: application/json" \
-d "{\"content\": \"我的第一条笔记\"}"
查看所有笔记:
浏览器访问 http://localhost:8080/notes
测试持久化:删除容器后重新启动,数据还在:
# 删除容器
docker rm -f sqlite-app
# 重新启动(用相同的 -v 参数)
docker run -d -p 8080:5000 -v "${PWD}/../data:/data" --name sqlite-app sqlite-demo:1.0
再访问 http://localhost:8080/notes,之前添加的数据仍然存在。
项目三:Python 数据处理脚本
目标: 用 Docker 运行一个批量处理 CSV 文件的脚本,处理完退出。
学到的技能: 短生命周期容器(处理完就退出)、挂载本地文件目录、--rm 自动清理。
3.1 项目文件结构
新建文件夹 csv-processor:
csv-processor/
├── process.py
├── requirements.txt
├── Dockerfile
├── input/
│ └── students.csv ← 待处理的数据
└── output/ ← 处理结果将输出到这里
3.2 准备测试数据
input/students.csv
name,score,subject
Alice,85,Math
Bob,92,Math
Charlie,78,Math
Alice,90,English
Bob,88,English
Charlie,95,English
Diana,70,Math
Diana,82,English
3.3 编写代码
process.py
import csv
import os
from collections import defaultdict
INPUT_DIR = "/data/input"
OUTPUT_DIR = "/data/output"
def main():
os.makedirs(OUTPUT_DIR, exist_ok=True)
# 读取所有 CSV 文件
all_files = [f for f in os.listdir(INPUT_DIR) if f.endswith(".csv")]
print(f"找到 {len(all_files)} 个文件:{all_files}")
for filename in all_files:
filepath = os.path.join(INPUT_DIR, filename)
process_file(filepath, filename)
print("全部处理完成。")
def process_file(filepath, filename):
scores = defaultdict(list)
with open(filepath, newline="", encoding="utf-8") as f:
reader = csv.DictReader(f)
for row in reader:
name = row["name"]
score = float(row["score"])
scores[name].append(score)
# 计算每人的平均分和总分
results = []
for name, score_list in scores.items():
results.append({
"name": name,
"count": len(score_list),
"total": sum(score_list),
"average": round(sum(score_list) / len(score_list), 2),
})
# 按平均分降序排列
results.sort(key=lambda x: x["average"], reverse=True)
# 写入结果文件
output_name = filename.replace(".csv", "_result.csv")
output_path = os.path.join(OUTPUT_DIR, output_name)
with open(output_path, "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=["name", "count", "total", "average"])
writer.writeheader()
writer.writerows(results)
print(f"[{filename}] 处理完成,结果已保存到 {output_name}")
for r in results:
print(f" {r['name']}: 平均分 {r['average']}")
if __name__ == "__main__":
main()
requirements.txt
# 本项目只用标准库,无需额外依赖
Dockerfile
FROM python:3.11-slim
WORKDIR /app
COPY process.py .
# 数据目录由外部挂载
# 注意,如果RUN时没有指定数据卷,则本地文件系统会创建匿名卷,并且不会随着容器删除而删除
# 可以通过 `docker volume ls` 查看匿名卷,`rm` 删除匿名卷
VOLUME ["/data/input", "/data/output"]
CMD ["python", "process.py"]
3.4 构建并运行
cd csv-processor
docker build -t csv-processor:1.0 .
运行脚本(处理完自动退出,--rm 自动删除容器):
# Git Bash 写法:
docker run --rm \
-v "$(pwd)/input:/data/input" \
-v "$(pwd)/output:/data/output" \
csv-processor:1.0
# PowerShell 写法:
docker run --rm `
-v "${PWD}/input:/data/input" `
-v "${PWD}/output:/data/output" `
csv-processor:1.0
3.5 查看结果
运行后查看 output/ 目录,应该有一个 students_result.csv 文件,内容如下:
name,count,total,average
Charlie,2,173,86.5
Bob,2,180,90.0
Alice,2,175,87.5
Diana,2,152,76.0
(实际排序按平均分降序)
常用调试技巧
查看容器日志
程序出错时第一步:看日志。
docker logs <容器名>
# 看最后 50 行
docker logs --tail 50 <容器名>
进入容器内部排查
docker exec -it <容器名> bash
# 进入后可以用 ls、cat 等命令检查文件是否正确复制进来
检查镜像层
docker history <镜像名>
修改代码后重新部署
Docker 不会自动感知代码变化,每次修改代码后需要:
# 1. 停止并删除旧容器
docker rm -f <容器名>
# 2. 重新构建镜像
docker build -t <镜像名> .
# 3. 重新运行
docker run -d -p 8080:5000 --name <容器名> <镜像名>
综合练习建议
完成三个项目后,尝试以下挑战:
- 修改项目一:添加一个
/time路由,返回当前服务器时间 - 扩展项目二:增加删除笔记的接口(DELETE /notes/)
- 改造项目三:让脚本接受环境变量来控制排序方式(升序/降序)
docker run --rm -e SORT_ORDER=asc -v ... csv-processor:1.0
下一步学习方向
掌握以上内容后,你已经能够应对大多数单服务的 Docker 部署需求。
继续深入可以学习:
- Docker Compose:用一个文件管理多个容器(如 Python + MySQL + Redis),是生产环境的标配
- Docker Hub 推送:将自己构建的镜像上传到仓库,让别人一行命令就能拉取运行
- 健康检查(HEALTHCHECK):在 Dockerfile 中配置,让 Docker 自动监控容器状态
- 多阶段构建(Multi-stage Build):减小镜像体积的进阶技巧
更多推荐




所有评论(0)