分类课程智能体AI
文章
订阅
分类课程AI导师
文章
价格
课程进度
14 / 14
上一节建立容器安全基线
自在学

© 2025 - 2026 株洲市自在学教育科技有限公司 版权所有

公网安备湘公网安备43020302000292号 | 湘ICP备2025148919号-1

关于我们隐私政策使用条款

© 2025 - 2026 株洲市自在学教育科技有限公司 版权所有

公网安备湘公网安备43020302000292号湘ICP备2025148919号-1

编程Docker 从入门到项目交付从构建到交付的完整演练

从构建到交付的完整演练

最后一节不引入新概念。我们从项目文件开始,完整执行配置检查、构建、启动、写入数据、重建验证、查看安全状态和清理。你可以把这段流程当作以后接手容器项目时的操作模板。

从理解到完整交付的 Docker 能力地图


开始前先明确验收条件

一次成功交付至少满足这些条件:

  1. Compose 配置能解析。
  2. 应用镜像能从源码构建。
  3. API 与 Redis 都进入健康状态。
  4. 可以创建并读取任务。
  5. 重建容器后数据仍存在。
  6. API 使用非 root 用户和只读根文件系统。
  7. 清理命令只处理当前项目资源。

这七项覆盖了配置、构建、运行、业务、数据、安全和资源归属。任何一项缺失,都只能说明“某个步骤成功”,不能说明项目已经可交付。

先画出本次交付的因果链

text
源码 + go.mod/go.sum + Dockerfile
              │ docker build
              ▼
      docker-task-api:1.0
              │ Compose 创建容器
              ▼
外部 8080 → API 服务 → redis:6379 → task-data 卷
              │
              └─ /health 同时验证 HTTP、DNS 与 Redis

后面的每条验收命令都应落在这张图的一条边或一个对象上。这样失败时能说清楚链路断在哪里。

建立一次交付记录

在真实项目中,建议记录:源码提交、构建时间、目标平台、镜像 ID/digest、Compose 项目名、最终配置摘要、验收结果和清理/回滚方式。课程用版本标签 1.0 和项目名 docker-course 演示最小形式。


知识点:执行前检查要证明“输入完整”

构建结果取决于 Dockerfile、构建上下文、源码与依赖清单;运行结果还依赖 Compose 解析后的配置。第一步只做只读检查,不创建任何 Docker 对象,目的是在状态变化前发现缺文件、变量插值或 YAML 结构问题。


实操:检查文件与配置

shell
$ ls -1
Dockerfile
compose.yaml
go.mod
go.sum
main.go
 
$ docker compose config --quiet

结果展示

第二条命令没有输出并正常结束,说明 Compose 文件语法和变量插值通过检查。它不能证明镜像一定能构建,所以还要继续。

继续输出可审查摘要:

shell
$ docker compose -p docker-course config --services
api
redis
$ docker compose -p docker-course config --volumes
task-data
$ docker compose -p docker-course config --images
docker-task-api:1.0
redis:8-alpine

结果应包含两个服务、一个逻辑卷和两张镜像。config 通过却缺少某个对象,说明 YAML 语法正确但业务声明仍不完整。


知识点:构建与启动要分开理解

up -d --build 把两步连在一起:先根据 build 配置得到 API 镜像,再根据 service 配置创建网络、卷和容器。命令方便,但日志中要分辨失败发生在构建阶段还是运行阶段。

构建失败时不会得到可运行的新镜像;容器启动失败时镜像可能完全正确。我们用 docker compose images、ps 和健康检查分别验收。


实操:构建并启动

给项目一个稳定名字,避免目录改名影响资源名称:

shell
$ docker compose -p docker-course up -d --build
[+] Running 4/4
 ✔ Network docker-course_default    Created
 ✔ Volume docker-course_task-data   Created
 ✔ Container docker-course-redis-1  Healthy
 ✔ Container docker-course-api-1    Started

查看状态:

shell
$ docker compose -p docker-course ps
NAME                    IMAGE                 SERVICE   STATUS                  PORTS
docker-course-api-1     docker-task-api:1.0   api       Up 6 seconds (healthy)  0.0.0.0:8080->8080/tcp
docker-course-redis-1   redis:8-alpine        redis     Up 11 seconds (healthy) 6379/tcp

结果展示

两个服务都达到 healthy。Redis 没有外部端口映射,API 对外提供 8080。

再确认镜像与项目对象的对应关系:

shell
$ docker compose -p docker-course images
CONTAINER                 REPOSITORY          TAG        IMAGE ID
docker-course-api-1       docker-task-api     1.0        ...
docker-course-redis-1     redis               8-alpine   ...
$ docker network inspect docker-course_default \
    --format '{{len .Containers}} containers attached'
2 containers attached

这证明两个 service 都接入同一项目网络。名称和输出格式可能随版本略有变化,核心判断是服务镜像正确、网络包含两个容器。


知识点:业务验收必须跨过真实依赖

访问首页只验证 API 自己能返回静态信息。创建任务会依次经过 JSON 解析、API 处理、容器 DNS、Redis 连接和卷上的数据持久化,因此更适合作为核心冒烟请求。

请求前先检查健康,避免把依赖尚未就绪的竞态误认为业务 bug:

shell
$ curl -fsS http://localhost:8080/health
{"status":"ok"}

实操:完成一次业务请求

shell
$ curl -s -X POST http://localhost:8080/tasks \
    -H 'Content-Type: application/json' \
    -d '{"title":"完成 Docker 课程"}'
{"title":"完成 Docker 课程","createdAt":"2026-07-15T02:39:53Z"}
 
$ curl -s http://localhost:8080/tasks
[{"title":"完成 Docker 课程","createdAt":"2026-07-15T02:39:53Z"}]

结果展示

POST 返回创建的任务,GET 读回同一标题。请求已经穿过端口映射、API 容器、Compose 网络和 Redis。

从 Redis 侧做交叉核对:

shell
$ docker compose -p docker-course exec redis redis-cli LLEN tasks
1

如果 GET 返回任务但 Redis 长度不符,应重新审视应用是不是读了别处缓存;本项目两端一致,证明任务确实进入 Redis。


知识点:持久化验收必须替换容器身份

只执行 restart 无法证明卷独立,因为同一个容器可写层也会在重启后保留。--force-recreate 让容器 ID 改变、命名卷保持,才真正验证数据不依赖旧容器。

重建前先记录两个 ID:

shell
$ docker compose -p docker-course ps -q api
<旧 API ID>
$ docker compose -p docker-course ps -q redis
<旧 Redis ID>

实操:重建容器并验证持久化

--force-recreate 会替换容器,但不会删除命名卷:

shell
$ docker compose -p docker-course up -d --force-recreate
[+] Running 2/2
 ✔ Container docker-course-redis-1  Healthy
 ✔ Container docker-course-api-1    Started
 
$ curl -s http://localhost:8080/tasks
[{"title":"完成 Docker 课程","createdAt":"2026-07-15T02:39:53Z"}]

结果展示

容器已重建,任务仍存在。数据属于 docker-course_task-data 卷,而不是旧 Redis 容器的可写层。

再次执行 ps -q,两个容器 ID 应与重建前不同;检查卷名则仍相同:

shell
$ docker volume ls --filter name=docker-course_task-data
DRIVER   VOLUME NAME
local    docker-course_task-data

“实例变、数据不变”是这一步真正要证明的因果关系。


知识点:交付物要同时验收功能与边界

一张能运行的镜像仍可能使用 root、带着编译器或允许写入整个根文件系统。安全状态要从实际容器读取,并用负向操作验证;安全检查成功后,还要继续完成正常业务回归。


实操:检查镜像与安全状态

shell
$ docker image ls docker-task-api:1.0 \
    --format '{{.Repository}}:{{.Tag}} {{.Size}}'
docker-task-api:1.0 21.9MB
 
$ docker inspect docker-course-api-1 --format \
    '{{.State.Health.Status}} user={{.Config.User}} readonly={{.HostConfig.ReadonlyRootfs}}'
healthy user=app readonly=true

镜像大小可能变化;健康状态、用户和只读状态应与输出一致。

继续检查权能和运行文件:

shell
$ docker inspect docker-course-api-1 --format \
    'caps={{json .HostConfig.CapDrop}} security={{json .HostConfig.SecurityOpt}}'
caps=["ALL"] security=["no-new-privileges:true"]
$ docker compose -p docker-course exec api sh -c \
    'test ! -e /usr/local/go/bin/go && echo compiler=absent; touch /blocked'
compiler=absent
touch: /blocked: Read-only file system

预期编译器不存在,写根文件系统失败。随后再次请求 /health,确认应用在这些限制下仍正常。


故障演练:证明你能从失败恢复

一门完整课程不能只在顺利路径结束。下面三个演练不需要引入新知识,目的是把排错流程变成能力。

演练一:错误的 Redis 端口

把 REDIS_ADDR 临时改为 redis:6399,执行 up -d。预测 API 会 running 但 unhealthy,Redis 仍 healthy。用最终配置、健康日志和 API 日志证明端口错误,再恢复 6379 并重放 /health。

演练二:外部端口冲突

先让另一个进程占用 8080,或把 APP_PORT 指向已占用端口。预测 API 容器创建/启动阶段会报端口绑定失败。用 APP_PORT=18080 重新执行 up -d,确认容器内端口仍是 8080,外部访问改到 18080。

演练三:依赖短暂不可用

启动成功后暂停 Redis,观察 /health 从 200 变 503;恢复 Redis,观察 API 在不重建镜像的情况下重新健康。解释为什么 depends_on 只控制首次启动顺序,应用运行期仍需要超时和恢复能力。

每次演练都按同一格式记录:预测、操作、证据、修复、回归。只要能独立完成这三条闭环,就已经不再是“只能照着成功命令输入”。完成演练并恢复 compose.yaml 与默认端口后,再进入清理阶段。


知识点:清理是交付流程的一部分

可重复项目不仅要能创建,还要能识别并移除自己创建的对象。清理前先把对象分成两类:可重建的容器、网络和镜像;可能包含唯一状态的数据卷。默认保留状态,只有明确确认后才删除。

删除数据前先做最后一次备份判断

本章任务数据是课程练习,可以在确认后删除。真实项目至少要问:最近一次备份何时完成、恢复是否验证、是否存在合规保留要求、当前卷是否仍被其他服务使用。down --volumes 的简短语法不代表决策也应该简短。


实操:先保留数据,再彻底清理

先演示常规停止。这个命令删除容器和项目网络,但保留数据卷:

shell
$ docker compose -p docker-course down
[+] Running 3/3
 ✔ Container docker-course-api-1    Removed
 ✔ Container docker-course-redis-1  Removed
 ✔ Network docker-course_default    Removed

重新启动后,任务仍应存在:

shell
$ docker compose -p docker-course up -d
$ curl -s http://localhost:8080/tasks
[{"title":"完成 Docker 课程","createdAt":"2026-07-15T02:39:53Z"}]

确认不再需要课程数据后,再删除项目卷:

shell
$ docker compose -p docker-course down --volumes
[+] Running 4/4
 ✔ Container docker-course-api-1     Removed
 ✔ Container docker-course-redis-1   Removed
 ✔ Volume docker-course_task-data    Removed
 ✔ Network docker-course_default     Removed

最后按需删除应用镜像:

shell
$ docker image rm docker-task-api:1.0
Untagged: docker-task-api:1.0

只有在确认数据不再需要时,才执行 down --volumes。项目名 -p docker-course 把清理范围限制在这套 Compose 资源,不要用全局 prune 代替项目清理。

结果展示:清理也要验收

shell
$ docker ps -a --filter label=com.docker.compose.project=docker-course
$ docker network ls --filter label=com.docker.compose.project=docker-course
$ docker volume ls --filter label=com.docker.compose.project=docker-course

在执行 down --volumes 后,三条查询都不应列出该项目资源。再用精确镜像名确认应用镜像是否按计划保留或删除。不要用“命令显示 Removed”替代最终状态核对。


你现在应该能独立完成什么

你已经走完一条完整路径:安装 Docker,运行现成镜像,理解镜像和容器状态,编写 Dockerfile,利用缓存与多阶段构建,使用数据卷和网络,把两个服务写进 Compose,再加入健康检查、排错、安全限制和发布准备。

接下来遇到新项目,可以按同一个顺序动手:

  1. 找到应用进程、监听端口、依赖和需要保存的数据。
  2. 先写出能构建的 Dockerfile,并检查运行用户和镜像内容。
  3. 把数据库等依赖拆成服务,用网络名连接。
  4. 把数据放入卷,把可变配置留在运行阶段。
  5. 用健康检查定义“可用”,用日志与 inspect 定位故障。
  6. 通过版本标签、digest 和多平台构建准备发布。
  7. 用最小权限和明确清理范围完成交付。

更重要的是,你已经拥有一套可迁移的推理方式:

  • 看到命令时,先判断它操作哪个 Docker 对象、改变哪个生命周期。
  • 看到故障时,先按配置、对象、进程、网络、存储和资源分层。
  • 看到“成功”时,追问证据是否覆盖了真实业务依赖。
  • 看到安全限制时,说明它阻断哪条风险,并同时验证正常功能。
  • 看到删除命令时,先区分可重建状态与唯一数据。

下一步可以给项目补上应用级优雅关闭、自动化测试、镜像扫描和 CI 多平台构建。若要学习 Kubernetes,也应把本课中的镜像、容器端口、卷、服务发现、健康检查和声明式配置逐一映射过去,而不是跳过 Docker 基础重新背另一套 YAML。


结课自测

1
强制重建 Redis 容器后,任务仍然存在。哪些设计共同促成了这个结果?
2
容器显示 running,但接口返回 503,最合理的第一步是什么?
  • 开始前先明确验收条件
    • 先画出本次交付的因果链
    • 建立一次交付记录
  • 知识点:执行前检查要证明“输入完整”
  • 实操:检查文件与配置
    • 结果展示
  • 知识点:构建与启动要分开理解
  • 实操:构建并启动
    • 结果展示
  • 知识点:业务验收必须跨过真实依赖
  • 实操:完成一次业务请求
    • 结果展示
  • 知识点:持久化验收必须替换容器身份
  • 实操:重建容器并验证持久化
    • 结果展示
  • 知识点:交付物要同时验收功能与边界
  • 实操:检查镜像与安全状态
  • 故障演练:证明你能从失败恢复
    • 演练一:错误的 Redis 端口
    • 演练二:外部端口冲突
    • 演练三:依赖短暂不可用
  • 知识点:清理是交付流程的一部分
    • 删除数据前先做最后一次备份判断
  • 实操:先保留数据,再彻底清理
    • 结果展示:清理也要验收
  • 你现在应该能独立完成什么
  • 结课自测

目录

  • 开始前先明确验收条件
    • 先画出本次交付的因果链
    • 建立一次交付记录
  • 知识点:执行前检查要证明“输入完整”
  • 实操:检查文件与配置
    • 结果展示
  • 知识点:构建与启动要分开理解
  • 实操:构建并启动
    • 结果展示
  • 知识点:业务验收必须跨过真实依赖
  • 实操:完成一次业务请求
    • 结果展示
  • 知识点:持久化验收必须替换容器身份
  • 实操:重建容器并验证持久化
    • 结果展示
  • 知识点:交付物要同时验收功能与边界
  • 实操:检查镜像与安全状态
  • 故障演练:证明你能从失败恢复
    • 演练一:错误的 Redis 端口
    • 演练二:外部端口冲突
    • 演练三:依赖短暂不可用
  • 知识点:清理是交付流程的一部分
    • 删除数据前先做最后一次备份判断
  • 实操:先保留数据,再彻底清理
    • 结果展示:清理也要验收
  • 你现在应该能独立完成什么
  • 结课自测