从零到精通:Docker容器化完整指南与实战练习

目录

引言

基础练习

[练习 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 作为基础镜像

  • 安装 nginxcurl

  • 创建一个简单的 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.tomlCargo.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 基础镜像并安装 flaskredis 等依赖。

  • 将应用代码拷贝到镜像中并暴露相应端口。

  • 配置容器的启动命令。

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

提交之后我们就可以前往相关地方查看我们上面这三个题目得分多少,下面的截图中可以很清晰的看到,我们上述的基础题作答是没有问题的。

总结

基础练习 重点夯实容器化的基本功:

  1. 基础镜像与命令:以Ubuntu为基础,演示了镜像定制、软件包安装、文件复制、端口暴露和前台进程启动的完整流程。

  2. Python应用构建:展示了如何在镜像中创建非root用户、管理Python依赖、设置环境变量,并以Flask应用为例实现了安全、可配置的容器化部署。

  3. Rust多阶段构建:利用多阶段构建大幅优化镜像体积,结合musl工具链实现静态编译,最终在极简的Alpine镜像中运行,体现了生产级镜像的优化思路。

专业练习 深入实际生产场景中的复杂需求:

  1. 存储管理与数据持久化:通过Docker Volume实现MySQL数据的持久化存储,并结合docker-compose与服务初始化脚本,确保数据库服务的状态与数据在容器生命周期之外得以保留。

  2. 网络管理与服务通信:基于自定义Bridge网络,构建了Python Web应用与Redis缓存服务的通信架构,演示了服务发现、内部网络隔离与端口映射的配置方法。

  3. 多容器编排实战:综合运用Golang、MySQL与Nginx,通过Docker Compose实现了完整的应用编排。涵盖了服务依赖管理、环境变量注入、反向代理配置以及自定义网络隔离,完整模拟了一个典型Web应用的后端架构。

相关推荐
OurBMC社区6 小时前
玩转OurBMC第二十三期:OurBMC之PCIe接口应用(下)
linux·运维·网络
Guheyunyi6 小时前
用电安全管理系统的三大系统架构
大数据·运维·人工智能·安全·架构·系统架构
weixin_46686 小时前
K8S-Configmap
linux·容器·kubernetes
weixin_307779136 小时前
Jenkins Pipeline: Multibranch 插件详解:现代CI/CD的多分支管理利器
运维·开发语言·自动化·jenkins·etl
刀刀是个萌妹子7 小时前
使用CloneZilla还原操作系统
linux·运维·服务器
代码不行的搬运工7 小时前
交换机和网卡的 PFC 机制工作原理与实例解析
运维·服务器·网络·算力网络
二狗哈7 小时前
Cesium快速入门22:fabric自定义着色器
运维·开发语言·前端·webgl·fabric·cesium·着色器
Kristen_YXQDN7 小时前
PyCharm 中 pytest 运行 python 测试文件报错:D:\Python_file\.venv\Scripts\python.exe: No module named pytest
运维·开发语言·python·pycharm·pytest
eddy-原7 小时前
全链路 DevOps 实战:基于 Jenkins、GitLab、Prometheus 与 SonarQube 的持续集成、部署、监控与优化
运维·jenkins