目录
[练习 1:基础镜像和命令使用](#练习 1:基础镜像和命令使用)
[练习 2:Python 应用构建](#练习 2:Python 应用构建)
[练习 3:Rust 多阶段构建](#练习 3:Rust 多阶段构建)
[练习 1: Docker 存储管理与 Volume 持久化](#练习 1: Docker 存储管理与 Volume 持久化)
[练习 2:Docker 网络管理与自定义 Bridge](#练习 2:Docker 网络管理与自定义 Bridge)
[练习 3:Docker Compose 多容器编排](#练习 3:Docker Compose 多容器编排)
引言
本文通过一套由浅入深、循序渐进的实战练习,系统性地展示了Docker的核心概念与实际应用。从最基础的镜像构建与容器运行,到复杂的多阶段构建优化;从单服务容器化,到多容器应用的网络配置与数据持久化编排,每一部分均配有清晰的代码示例、分步操作说明和验证方法。无论你是刚刚接触容器技术的初学者,还是希望巩固和深化实战经验的开发者,都能通过这些练习快速掌握Docker的核心技能,并搭建起可在生产环境中参考的容器化应用模型。
题目来自2025 第二期 Docker 训练营
2025 第二期 Docker 训练营地址: https://opencamp.cn/Docker/camp/202502/register?code=dxJSEQUgrBdWs
基础练习
练习 1:基础镜像和命令使用
-
使用
ubuntu:22.04作为基础镜像 -
安装
nginx和curl包 -
创建一个简单的 HTML 文件,内容为 "Hello Docker!"
-
将该 HTML 文件复制到 Nginx 的默认网站目录
-
暴露 80 端口并启动 Nginx 服务
给Dockerfile文件添加以下代码
FROM ubuntu:22.04
# 在这里编写你的 Dockerfile 指令
# 避免在交互式安装时卡住
ENV DEBIAN_FRONTEND=noninteractive
# 更新 apt 并安装 nginx、curl;安装后清理 apt 缓存以减小镜像体积
RUN apt-get update \
&& apt-get install -y --no-install-recommends nginx curl \
&& rm -rf /var/lib/apt/lists/*
# 确保 nginx 默认网站目录存在(通常已存在),并创建一个简单的 HTML 文件
RUN mkdir -p /var/www/html \
&& cat > /var/www/html/index.html <<'EOF'
Hello Docker!
EOF
# 暴露 HTTP 默认端口
EXPOSE 80
# 以非守护(foreground)方式启动 nginx,使容器持续运行
CMD ["nginx", "-g", "daemon off;"]
进入 exercise1 目录,构建镜像并运行容器:
➜ /workspace git:(main) cd exercise1
➜ exercise1 git:(main) docker build -t exercise1 .

➜ exercise1 git:(main) docker run -d -p 8080:80 exercise1

使用 curl 测试容器中的网站:
➜ exercise1 git:(main) curl http://127.0.0.1:8080 | grep -q "Hello Docker"

如果命令执行无输出,表示网页内容包含 "Hello Docker!",测试通过。
练习 2:Python 应用构建
-
使用
python:3.11-slim作为基础镜像 -
安装应用依赖并创建非 root 用户运行应用
-
设置工作目录并复制应用代码
-
设置环境变量(例如禁用字节码生成、指定应用端口等)
-
暴露应用端口并启动服务
编写 Dockerfile:
# 1. 使用 python:3.11-slim 作为基础镜像
FROM python:3.11-slim
# 2. 设置环境变量
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
APP_PORT=5000
# 3. 创建非 root 用户
RUN adduser --disabled-password --gecos "" appuser
# 4. 设置工作目录
WORKDIR /app
# 5. 先复制依赖文件(利用 Docker 缓存)
COPY requirements.txt .
# 6. 安装依赖
RUN pip install --no-cache-dir -r requirements.txt
# 7. 复制应用代码
COPY app.py .
# 8. 修改文件权限(确保非 root 用户可访问)
RUN chown -R appuser:appuser /app
# 9. 切换到非 root 用户
USER appuser
# 10. 暴露端口
EXPOSE 5000
# 11. 启动应用
CMD ["python", "app.py"]
应用代码 app.py 如下:
from flask import Flask, jsonify
import os
app = Flask(__name__)
@app.route('/')
def hello():
return jsonify({
'message': 'Hello Docker!',
'status': 'success'
})
@app.route('/health')
def health():
return jsonify({
'status': 'healthy',
'version': '1.0.0'
})
if __name__ == '__main__':
port = int(os.environ.get('PORT', 5000))
app.run(host='0.0.0.0', port=port)
依赖文件 requirements.txt 包含:
Flask==3.0.0
Werkzeug==3.0.1
click==8.1.7
itsdangerous==2.1.2
Jinja2==3.1.2
MarkupSafe==2.1.3
进入 exercise2 目录,构建镜像并运行容器:
➜ /workspace git:(main) cd exercise2
➜ exercise2 git:(main) docker build -t exercise2 .

使用浏览器或 curl 访问 http://localhost:5000/,应返回如下 JSON 响应:
➜ exercise2 git:(main) docker run -d -p 5000:5000 exercise2

命令行界面测试镜像

练习 3:Rust 多阶段构建
- 使用多阶段构建优化 Rust 应用的 Docker 镜像。
第一阶段(Builder 阶段):
-
使用
rust:1.75-slim作为基础镜像 -
设置工作目录
-
安装
musl-tools并添加x86_64-unknown-linux-musl目标以支持静态链接 -
复制
Cargo.toml、Cargo.lock(如果存在)及所有源代码 -
使用
cargo build --release --target x86_64-unknown-linux-musl构建应用
第二阶段(Runtime 阶段):
-
使用
alpine:latest作为基础镜像 -
安装运行时依赖(如
tzdata) -
从第一阶段复制编译好的二进制文件
-
设置工作目录并运行应用
Dockerfile 文件内容:
# =========================
# Builder
# =========================
FROM rust:1.75-slim AS builder
WORKDIR /app
RUN apt-get update \
&& apt-get install -y --no-install-recommends musl-tools \
&& rm -rf /var/lib/apt/lists/*
RUN rustup target add x86_64-unknown-linux-musl
# 直接复制全部源码(不做任何缓存优化)
COPY . .
# 构建(明确、直接、无歧义)
RUN cargo build --release --target x86_64-unknown-linux-musl
# 打印产物,作为铁证
RUN echo "=== BUILT FILES ===" \
&& ls -lh target/x86_64-unknown-linux-musl/release
# =========================
# Runtime
# =========================
FROM alpine:latest
RUN apk add --no-cache tzdata
WORKDIR /app
# ⚠️ 这里的 hello-rust 必须等于 Cargo.toml 的 package.name
COPY --from=builder /app/target/x86_64-unknown-linux-musl/release/rust-docker-example ./app
CMD ["./app"]
项目文件结构包括 Cargo.toml:
[package]
name = "rust-docker-example"
version = "0.1.0"
edition = "2021"
[dependencies]
chrono = "0.4"
以及应用源代码 main.rs:
use chrono::Local;
fn main() {
// 获取当前时间
let now = Local::now();
let timestamp = now.format("%Y-%m-%d %H:%M:%S").to_string();
// 直接输出到 stdout
println!("Hello from Rust!");
println!("当前时间: {}", timestamp);
}
进入 exercise3 目录,构建镜像并运行容器验证:
➜ /workspace git:(main) cd exercise3
➜ exercise3 git:(main) docker build -t rust-exercise3 .

➜ exercise3 git:(main) docker run rust-exercise3

可看到输出 "Hello from Rust !" 及当前时间戳,表示镜像构建成功。
基础部分提交查看分数
随后我们利用git相关命令进行提交
git add .
git commit -m "feat: ***"
git push
提交之后我们就可以前往相关地方查看我们上面这三个题目一共得分多少,下面的截图中可以很清晰的看到,我们上述的基础题作答是没有问题的。

专业练习
练习 1: Docker 存储管理与 Volume 持久化
-
使用 Docker Volume 持久化 MySQL 数据,确保容器重启后数据不丢失
-
通过
init.sql初始化脚本创建testdb数据库 -
使用环境变量设置 MySQL
root密码 -
提供测试脚本验证数据持久化
-
使用 Docker Compose 部署
docker-compose.yml 文件内容:
version: '3.8'
# 在这里编写你的 docker-compose.yml 文件
services:
mysql:
image: mysql:8.0
container_name: mysql-db
restart: unless-stopped
environment:
# 通过环境变量设置 root 密码(作业要求)
MYSQL_ROOT_PASSWORD: "123456"
ports:
- "3306:3306" # 将 MySQL 暴露到主机 3306
volumes:
- mysql-data:/var/lib/mysql # named volume,持久化 MySQL 数据目录
- ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro # 首次启动自动执行
volumes:
mysql-data:
初始化脚本 init.sql:
CREATE DATABASE IF NOT EXISTS testdb;
USE `testdb`;
CREATE TABLE IF NOT EXISTS sample (
id INT AUTO_INCREMENT PRIMARY KEY,
msg VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO sample (msg) VALUES ('Hello, persistent world');
启动服务并测试数据持久化:
docker compose up -d
docker compose exec mysql mysql -uroot -p123456 -e "SHOW DATABASES;"

执行后可看到 testdb 数据库存在,说明通过初始化脚本创建的数据库和表已生效,并且数据已保存在卷中。
练习 2:Docker 网络管理与自定义 Bridge
-
构建一个支持 Redis 计数功能的 Python Web 应用镜像。
-
选择合适的 Python 基础镜像并安装
flask、redis等依赖。 -
将应用代码拷贝到镜像中并暴露相应端口。
-
配置容器的启动命令。
Dockerfile:
FROM python:3.10-slim
# 在这里编写你的 Dockerfile
# 设置工作目录
WORKDIR /app
# 复制依赖文件并安装
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 复制应用代码
COPY app.py .
# 暴露端口
EXPOSE 5000
# 容器启动命令
CMD ["python", "app.py"]
应用代码 app.py:
from flask import Flask
import redis
app = Flask(__name__)
r = redis.Redis(host='redis', port=6379, decode_responses=True)
@app.route('/ping')
def ping():
count = r.incr('count')
return f'PONG {count}\n', 200
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
requirements.txt 内容:
version: '3.8'
# 在这里编写你的 docker-compose.yml 文件
services:
redis:
image: redis:7
container_name: redis
networks:
- mynet
web:
build: .
container_name: web
ports:
- "5000:5000"
depends_on:
- redis
networks:
- mynet
networks:
mynet:
driver: bridge
docker-compose.yml:
➜ exercise2 git:(main) docker compose up -d
启动服务并测试:
➜ exercise2 git:(main) docker compose up -d

使用 curl 访问 http://localhost:5000/ping,每次请求会返回带有递增计数的 PONG 响应,例如:
➜ exercise2 git:(main) curl http://localhost:5000/ping

练习 3:Docker Compose 多容器编排
-
使用 Docker Compose 编排 Golang Web 服务、MySQL 和 Nginx 三个服务。
-
Golang Web 服务实现
/count路径,计数值存储在 MySQL 数据库中。 -
使用 Nginx 反向代理,将主机的 8080 端口转发到 Golang 服务的 5000 端口。
-
各服务通过自定义网络
app-network互联,Web 服务仅对 Nginx 暴露接口。 -
提供测试脚本验证计数功能。
Golang 应用
应用目录 ./app 下的 Dockerfile:
FROM golang:1.21-alpine AS builder
# 在这里编写你的 Dockerfile
WORKDIR /app
# 先复制依赖描述文件
COPY go.mod go.sum ./
RUN go mod download
# 再复制源码
COPY main.go ./
RUN go build -o app
EXPOSE 5000
CMD ["./app"]
Go 模块文件 go.mod:
module counter-app
go 1.21
require github.com/go-sql-driver/mysql v1.7.1
./app/go.sum
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
源代码 main.go:
package main
import (
"database/sql"
"fmt"
"log"
"net/http"
"os"
"time"
_ "github.com/go-sql-driver/mysql"
)
var db *sql.DB
func main() {
dsn := os.Getenv("MYSQL_DSN")
if dsn == "" {
dsn = "root:123456@tcp(mysql:3306)/counter"
}
var err error
// 等待 MySQL 启动
for i := 0; i < 10; i++ {
db, err = sql.Open("mysql", dsn)
if err == nil && db.Ping() == nil {
break
}
time.Sleep(2 * time.Second)
}
if err != nil {
log.Fatal("数据库连接失败:", err)
}
_, _ = db.Exec(`CREATE TABLE IF NOT EXISTS counter (id INT PRIMARY KEY, value INT)`)
_, _ = db.Exec(`INSERT IGNORE INTO counter (id, value) VALUES (1, 0)`)
http.HandleFunc("/count", countHandler)
fmt.Println("Server running on :5000")
log.Fatal(http.ListenAndServe(":5000", nil))
}
func countHandler(w http.ResponseWriter, r *http.Request) {
_, _ = db.Exec(`UPDATE counter SET value = value + 1 WHERE id = 1`)
var value int
_ = db.QueryRow(`SELECT value FROM counter WHERE id = 1`).Scan(&value)
fmt.Fprintf(w, "Count: %d\n", value)
}
Nginx 配置文件 ./nginx/nginx.conf:
events {}
http {
server {
listen 8080;
location / {
proxy_pass http://app:5000;
}
}
}
docker-compose.yml 文件:
version: '3.8'
# 在这里编写你的 docker-compose.yml 文件
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: 123456
MYSQL_DATABASE: testdb
volumes:
- mysql-data:/var/lib/mysql
networks:
- app-network
app:
build: ./app
environment:
MYSQL_DSN: root:123456@tcp(mysql:3306)/testdb
depends_on:
- mysql
networks:
- app-network
nginx:
image: nginx:1.25
ports:
- "8080:8080"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- app
networks:
- app-network
volumes:
mysql-data:
networks:
app-network:
driver: bridge
启动并构建服务:
docker compose up -d --build

测试计数功能:
➜ exercise3 git:(main) curl http://localhost:8080/count

每次访问会返回递增的计数值
专业部分提交查看分数
随后我们利用git相关命令进行提交
git add .
git commit -m "feat: ***"
git push
提交之后我们就可以前往相关地方查看我们上面这三个题目得分多少,下面的截图中可以很清晰的看到,我们上述的基础题作答是没有问题的。

总结
基础练习 重点夯实容器化的基本功:
-
基础镜像与命令:以Ubuntu为基础,演示了镜像定制、软件包安装、文件复制、端口暴露和前台进程启动的完整流程。
-
Python应用构建:展示了如何在镜像中创建非root用户、管理Python依赖、设置环境变量,并以Flask应用为例实现了安全、可配置的容器化部署。
-
Rust多阶段构建:利用多阶段构建大幅优化镜像体积,结合
musl工具链实现静态编译,最终在极简的Alpine镜像中运行,体现了生产级镜像的优化思路。
专业练习 深入实际生产场景中的复杂需求:
-
存储管理与数据持久化:通过Docker Volume实现MySQL数据的持久化存储,并结合
docker-compose与服务初始化脚本,确保数据库服务的状态与数据在容器生命周期之外得以保留。 -
网络管理与服务通信:基于自定义Bridge网络,构建了Python Web应用与Redis缓存服务的通信架构,演示了服务发现、内部网络隔离与端口映射的配置方法。
-
多容器编排实战:综合运用Golang、MySQL与Nginx,通过Docker Compose实现了完整的应用编排。涵盖了服务依赖管理、环境变量注入、反向代理配置以及自定义网络隔离,完整模拟了一个典型Web应用的后端架构。