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

一次成功交付至少满足这些条件:
这七项覆盖了配置、构建、运行、业务、数据、安全和资源归属。任何一项缺失,都只能说明“某个步骤成功”,不能说明项目已经可交付。
源码 + 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 结构问题。
$ ls -1
Dockerfile
compose.yaml
go.mod
go.sum
main.go
$ docker compose config --quiet第二条命令没有输出并正常结束,说明 Compose 文件语法和变量插值通过检查。它不能证明镜像一定能构建,所以还要继续。
继续输出可审查摘要:
$ 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 和健康检查分别验收。
给项目一个稳定名字,避免目录改名影响资源名称:
$ 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查看状态:
$ 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。
再确认镜像与项目对象的对应关系:
$ 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:
$ curl -fsS http://localhost:8080/health
{"status":"ok"}$ 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 侧做交叉核对:
$ docker compose -p docker-course exec redis redis-cli LLEN tasks
1如果 GET 返回任务但 Redis 长度不符,应重新审视应用是不是读了别处缓存;本项目两端一致,证明任务确实进入 Redis。
只执行 restart 无法证明卷独立,因为同一个容器可写层也会在重启后保留。--force-recreate 让容器 ID 改变、命名卷保持,才真正验证数据不依赖旧容器。
重建前先记录两个 ID:
$ docker compose -p docker-course ps -q api
<旧 API ID>
$ docker compose -p docker-course ps -q redis
<旧 Redis ID>--force-recreate 会替换容器,但不会删除命名卷:
$ 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 应与重建前不同;检查卷名则仍相同:
$ docker volume ls --filter name=docker-course_task-data
DRIVER VOLUME NAME
local docker-course_task-data“实例变、数据不变”是这一步真正要证明的因果关系。
一张能运行的镜像仍可能使用 root、带着编译器或允许写入整个根文件系统。安全状态要从实际容器读取,并用负向操作验证;安全检查成功后,还要继续完成正常业务回归。
$ 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镜像大小可能变化;健康状态、用户和只读状态应与输出一致。
继续检查权能和运行文件:
$ 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_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 的简短语法不代表决策也应该简短。
先演示常规停止。这个命令删除容器和项目网络,但保留数据卷:
$ 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重新启动后,任务仍应存在:
$ docker compose -p docker-course up -d
$ curl -s http://localhost:8080/tasks
[{"title":"完成 Docker 课程","createdAt":"2026-07-15T02:39:53Z"}]确认不再需要课程数据后,再删除项目卷:
$ 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最后按需删除应用镜像:
$ docker image rm docker-task-api:1.0
Untagged: docker-task-api:1.0只有在确认数据不再需要时,才执行 down --volumes。项目名 -p docker-course 把清理范围限制在这套 Compose 资源,不要用全局 prune 代替项目清理。
$ 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,再加入健康检查、排错、安全限制和发布准备。
接下来遇到新项目,可以按同一个顺序动手:
更重要的是,你已经拥有一套可迁移的推理方式:
下一步可以给项目补上应用级优雅关闭、自动化测试、镜像扫描和 CI 多平台构建。若要学习 Kubernetes,也应把本课中的镜像、容器端口、卷、服务发现、健康检查和声明式配置逐一映射过去,而不是跳过 Docker 基础重新背另一套 YAML。