在前面的文章中,我们已经介绍了 Dockerfile 和 Docker Compose。
Dockerfile 解决的是「如何定义一个镜像」,Compose 解决的是「如何运行和管理多个容器」。
但一个完整的 Docker 应用交付流程还缺少一个重要环节:
Dockerfile
↓
docker build
↓
Docker Image
↓
docker tag
↓
docker push
↓
Container Registry
↓
docker pull
↓
服务器
↓
docker run / docker compose
这就是 Docker 镜像从「构建」到「发布」再到「部署」的完整链路。
本文重点介绍 docker build、docker tag、docker login、docker push、docker pull 以及 docker buildx build,并结合一个 PHP 应用说明实际项目中的镜像发布流程。
1. Docker 镜像发布的完整流程
一个 Docker 镜像从代码进入服务器,通常经历以下几个阶段:
开发环境
│
▼
Dockerfile
│
│ docker build
▼
Docker Image
│
│ docker tag
▼
Registry Image
│
│ docker push
▼
Container Registry
│
│ docker pull
▼
部署服务器
│
│ docker run
│ 或 docker compose
▼
Container
其中每个命令负责不同的事情:
| 命令 | 作用 |
|---|---|
docker build | 根据 Dockerfile 构建镜像 |
docker tag | 给镜像增加名称和版本标签 |
docker login | 登录镜像仓库 |
docker push | 将镜像上传到镜像仓库 |
docker pull | 从镜像仓库下载镜像 |
docker run | 根据镜像创建并启动容器 |
docker buildx build | 使用 BuildKit/Buildx 构建镜像,支持更多高级能力 |
Docker 官方当前的 docker build 默认使用 Buildx/BuildKit;传统 legacy builder 已经不再是 Linux 容器构建的默认路径。
因此,理解这条链路比单独记住几个命令更加重要。
2. docker build:构建 Docker 镜像
最基本的构建命令:
docker build .
这里的 . 表示 Build Context,即 Docker 构建时可以访问的目录。
如果当前目录存在:
project/
├── Dockerfile
├── composer.json
├── composer.lock
├── app/
└── public/
执行:
docker build .
Docker 会读取当前目录作为构建上下文,并根据其中的 Dockerfile 构建镜像。Docker 官方文档也将构建上下文定义为 docker build 最后一个参数所指定的路径或 URL。
2.1 指定镜像名称
实际开发中一般不会只使用:
docker build .
而是直接指定镜像名称:
docker build -t my-app .
其中:
-t
表示 --tag。
因此:
docker build -t my-app .
可以理解为:
Dockerfile
↓
构建镜像
↓
my-app:latest
如果指定版本:
docker build -t my-app:1.0.0 .
最终得到:
my-app:1.0.0
Docker 镜像引用通常遵循:
[REGISTRY_HOST[:PORT]/]NAMESPACE/REPOSITORY[:TAG]
例如:
nginx:1.27
或者:
registry.example.com/team/my-app:1.0.0
如果没有指定 Registry,Docker 默认使用 Docker Hub;如果没有指定 Tag,则默认使用 latest。
2.2 指定 Dockerfile
默认情况下,Docker 会读取:
Dockerfile
如果项目存在多个 Dockerfile:
Dockerfile
Dockerfile.dev
Dockerfile.prod
可以通过 -f 指定:
docker build \
-f Dockerfile.prod \
-t my-app:1.0.0 .
这里:
-f Dockerfile.prod
指定构建文件。
而最后的:
.
仍然是 Build Context。
这两个概念不要混淆。
3. Build Context 与 .dockerignore
docker build 的最后一个参数决定 Build Context:
docker build -t my-app .
这里的:
.
表示当前目录。
如果项目很大:
project/
├── .git/
├── node_modules/
├── vendor/
├── logs/
├── runtime/
├── storage/
├── Dockerfile
└── app/
直接把整个目录作为 Context 会增加不必要的数据传输和构建处理。
因此应该使用:
.dockerignore
例如:
.git
.gitignore
.env
node_modules
vendor
runtime
storage
*.log
这样 Docker 在构建时就不会把这些内容纳入 Build Context。
对于 PHP 项目尤其需要注意:
vendor/
是否应该排除,要根据 Dockerfile 的构建方式决定。
如果 Dockerfile 内部执行:
RUN composer install
通常应该排除本地 vendor。
如果项目明确要求把宿主机已经构建好的 vendor 复制到镜像中,则不能简单排除。
因此 .dockerignore 应该服务于 Dockerfile,而不是机械地复制一份通用模板。
4. Docker Build 的常用参数
4.1 指定 Tag
docker build \
-t my-app:1.0.0 \
.
4.2 指定 Dockerfile
docker build \
-f Dockerfile.prod \
-t my-app:1.0.0 \
.
4.3 指定多个 Tag
同一个镜像可以拥有多个 Tag:
docker build \
-t my-app:1.0.0 \
-t my-app:latest \
.
这样:
my-app:1.0.0
my-app:latest
可以指向同一个构建结果。
4.4 使用 Build Argument
Dockerfile:
ARG PHP_VERSION=8.3
FROM php:${PHP_VERSION}-cli
构建:
docker build \
--build-arg PHP_VERSION=8.4 \
-t my-app:1.0.0 \
.
需要注意,ARG 适合构建参数,不适合传递密码、Token 等敏感信息。
4.5 强制重新拉取基础镜像
如果希望构建时重新检查 FROM 使用的基础镜像:
docker build \
--pull \
-t my-app:1.0.0 \
.
这对于基础镜像更新、安全补丁检查比较有用。
4.6 不使用缓存
如果需要完全重新构建:
docker build \
--no-cache \
-t my-app:1.0.0 \
.
通常不要把 --no-cache 当成日常构建参数。
正常开发应该尽量利用构建缓存,否则每次都会重复执行耗时步骤。
5. Docker Image Tag:给镜像命名
构建完成后:
docker images
可能看到:
REPOSITORY TAG IMAGE ID
my-app latest abcdef123456
如果准备上传到镜像仓库,就需要使用符合 Registry 路径的名称。
假设镜像仓库:
registry.example.com
项目:
team/my-app
版本:
1.0.0
那么完整镜像名称:
registry.example.com/team/my-app:1.0.0
可以使用:
docker tag my-app:latest \
registry.example.com/team/my-app:1.0.0
docker tag 并不会重新构建镜像,也不会复制一份完整镜像。
它只是给现有镜像增加一个新的引用。Docker 官方文档也明确说明,Tag 是指向现有镜像的引用。
例如:
┌── my-app:latest
Image ──────────────┤
└── registry.example.com/team/my-app:1.0.0
因此可以放心给同一个镜像增加多个 Tag。
6. docker login:登录镜像仓库
在 Push 之前,通常需要登录 Registry:
docker login
默认登录 Docker Hub。
如果使用私有 Registry:
docker login registry.example.com
然后输入认证信息。
登录成功后,Docker 会保存对应的 Registry 凭据,用于后续 Push、Pull 等操作。
实际项目中,不建议把密码直接写进命令:
docker login -u root -p 123456
因为命令行参数可能被系统历史记录或进程信息暴露。
CI/CD 环境也应该使用平台提供的 Secret、Credential 或 Token 机制,而不是把密码直接写进脚本。
7. docker push:上传镜像
完成:
build
↓
tag
↓
login
之后,就可以 Push:
docker push registry.example.com/team/my-app:1.0.0
Docker 会把镜像需要的 Layers 上传到 Registry。
Docker 官方文档说明,Push 上传的是镜像的各个 Layer;已经存在于 Registry 中的 Layer 可以复用,因此后续推送同一基础镜像时通常不需要重复上传全部数据。
例如:
docker push registry.example.com/team/my-app:1.0.0
可能看到:
Layer 1: Already exists
Layer 2: Pushed
Layer 3: Pushed
Layer 4: Already exists
1.0.0: digest: sha256:xxxx...
其中:
Already exists
说明 Registry 中已经存在对应 Layer。
最后出现的:
digest: sha256:...
非常重要。
Digest 是镜像内容的内容寻址标识,相比 Tag,它具有更强的不可变性。
8. Tag 与 Digest 的区别
很多 Docker 初学者只知道:
latest
1.0.0
1.0.1
但生产环境还应该理解 Digest。
例如:
registry.example.com/team/my-app:1.0.0
是 Tag。
而:
registry.example.com/team/my-app@sha256:abcdef...
是 Digest 引用。
两者的核心区别:
| 方式 | 特点 |
|---|---|
| Tag | 人类容易理解,可以重新指向其他镜像 |
| Digest | 对应具体镜像内容,不随 Tag 改变 |
latest | 只是一个普通 Tag,不代表「最新构建结果」这一强语义 |
因此生产环境不应该简单地认为:
latest = 最新稳定版本
latest 只是默认 Tag。
例如:
my-app:latest
今天可能指向:
sha256:aaa...
明天重新 Push 后可能变成:
sha256:bbb...
这也是生产部署中不推荐完全依赖 latest 的原因。
9. docker pull:从 Registry 获取镜像
服务器部署时,通常执行:
docker pull registry.example.com/team/my-app:1.0.0
流程:
Container Registry
│
│ docker pull
▼
Docker Host
│
▼
Local Image
查看:
docker images
然后:
docker run \
-d \
--name my-app \
-p 9501:9501 \
registry.example.com/team/my-app:1.0.0
如果使用 Compose:
services:
app:
image: registry.example.com/team/my-app:1.0.0
然后:
docker compose pull
docker compose up -d
这样部署服务器就不需要拥有项目源代码,也不需要执行 Dockerfile 构建过程。
它只需要:
Pull Image
↓
Run Container
这正是容器镜像在 CI/CD 中非常重要的原因。
10. Docker Registry 是什么
前面出现的:
registry.example.com/team/my-app:1.0.0
实际上就是一个镜像仓库地址。
Registry 的职责是:
保存 Docker Image
↓
提供 Push
↓
提供 Pull
↓
供不同机器共享镜像
常见 Registry 包括:
Docker Hub
GitHub Container Registry
GitLab Container Registry
云厂商容器镜像服务
Harbor
自建 Docker Registry
对于企业内部环境,还可以部署自己的私有 Registry。
例如:
registry.example.com
项目:
team/my-app
那么完整地址:
registry.example.com/team/my-app:1.0.0
开发机:
docker push
服务器:
docker pull
从而形成:
Developer
│
│ docker build
▼
Local Image
│
│ docker push
▼
Registry
│
│ docker pull
▼
Production Server
11. docker buildx:构建生产级镜像
现代 Docker 构建体系中,Buildx 是非常重要的工具。
最简单的构建:
docker buildx build \
-t my-app:1.0.0 \
.
如果希望直接 Push:
docker buildx build \
-t registry.example.com/team/my-app:1.0.0 \
--push \
.
这里的:
--push
表示构建完成后直接将结果推送到 Registry。
Docker 官方将 --push 定义为 Registry exporter 的快捷方式,会把 BuildKit 构建结果直接推送到 Registry。
因此:
docker buildx build \
-t registry.example.com/team/my-app:1.0.0 \
--push \
.
可以把:
docker build
docker tag
docker push
在一定程度上合并为一次构建发布操作。
11.1 为什么使用 Buildx
Buildx 基于 BuildKit,可以提供:
更先进的构建缓存
多阶段构建
多平台构建
Secret Mount
SSH Mount
SBOM
Provenance
直接 Push Registry
例如:
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t registry.example.com/team/my-app:1.0.0 \
--push \
.
这样可以构建:
linux/amd64
linux/arm64
两个平台的镜像,并将结果推送到 Registry。
Docker 官方文档将 --platform、--push、--sbom、--provenance 等作为 Buildx 的核心构建能力。
12. 多平台镜像构建
现在服务器 CPU 架构并不只有 amd64。
常见架构包括:
linux/amd64
linux/arm64
例如:
Intel / AMD Server
↓
linux/amd64
Apple Silicon
↓
linux/arm64
ARM Server
↓
linux/arm64
如果开发机器是 ARM,而生产服务器是 AMD64,就需要注意镜像架构兼容性。
可以使用:
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t registry.example.com/team/my-app:1.0.0 \
--push \
.
Registry 中最终可以保存一个多平台镜像索引。
部署机器执行:
docker pull registry.example.com/team/my-app:1.0.0
Docker 会根据当前机器平台选择对应镜像。
这也是现代 Docker 构建体系相比传统单平台 docker build 的重要优势。
13. 镜像构建与 CI/CD
有了前面的知识,一个完整 CI/CD 流程就比较清晰了。
假设 Git 仓库:
GitHub / GitLab
代码提交:
git push
↓
CI
↓
docker buildx build
↓
镜像测试
↓
docker push
↓
Container Registry
↓
服务器
↓
docker pull
↓
docker compose up -d
例如 GitHub Actions 中可以形成:
name: Build Docker Image
on:
push:
tags:
- "v*"
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Login Registry
run: |
docker login \
-u "${{ secrets.REGISTRY_USERNAME }}" \
-p "${{ secrets.REGISTRY_PASSWORD }}"
- name: Build and Push
run: |
docker buildx build \
--platform linux/amd64 \
-t registry.example.com/team/my-app:${GITHUB_REF_NAME} \
--push \
.
实际生产环境应该进一步使用官方推荐的 Registry 登录 Action、Buildx Action,并通过 CI 平台 Secret 管理凭据。
最终形成:
Git Tag
v1.0.0
│
▼
CI Build
│
▼
Docker Image
│
▼
Registry
│
▼
Production
14. 一个完整的镜像发布案例
假设项目结构:
project/
├── Dockerfile
├── .dockerignore
├── composer.json
├── composer.lock
├── app/
├── config/
└── public/
镜像仓库:
registry.example.com/team/my-app
版本:
1.2.0
第一步:构建
docker build \
-t my-app:1.2.0 \
.
第二步:检查
docker images my-app
然后本地运行:
docker run \
-d \
--name my-app \
-p 9501:9501 \
my-app:1.2.0
确认应用正常。
第三步:Tag
docker tag \
my-app:1.2.0 \
registry.example.com/team/my-app:1.2.0
第四步:登录 Registry
docker login registry.example.com
第五步:Push
docker push \
registry.example.com/team/my-app:1.2.0
第六步:服务器 Pull
docker login registry.example.com
docker pull \
registry.example.com/team/my-app:1.2.0
第七步:启动
docker run \
-d \
--name my-app \
-p 9501:9501 \
registry.example.com/team/my-app:1.2.0
完整流程:
源码
↓
Dockerfile
↓
docker build
↓
my-app:1.2.0
↓
docker tag
↓
registry.example.com/team/my-app:1.2.0
↓
docker push
↓
Registry
↓
docker pull
↓
Production Server
↓
docker run
这就是最基础、也是最重要的 Docker 镜像交付流程。
15. Build、Push 常见问题
15.1 为什么 Push 前必须 Tag?
因为 Registry 需要知道:
镜像应该上传到哪个 Registry
哪个 Namespace
哪个 Repository
哪个 Tag
例如:
registry.example.com/team/my-app:1.0.0
而:
my-app:1.0.0
只代表本地镜像名称,并没有明确指定远程 Registry。
所以通常需要:
docker tag \
my-app:1.0.0 \
registry.example.com/team/my-app:1.0.0
然后:
docker push \
registry.example.com/team/my-app:1.0.0
15.2 为什么 Push 很慢?
Docker Push 按 Layer 上传。
如果某些 Layer 已经存在:
Already exists
就不需要重新上传。
如果 Dockerfile 设计不合理,经常导致基础层之后的大 Layer 不断变化,就会增加 Push 成本。
因此:
合理 Dockerfile
↓
合理 Layer
↓
更好的 Build Cache
↓
更高效的 Push
Docker 官方文档说明,Push 默认会并发上传多个 Layer;在低带宽环境下,可以通过 Docker Daemon 的 max-concurrent-uploads 调整并发上传数量。
15.3 为什么服务器不需要 Dockerfile?
因为服务器真正需要的是:
Image
而不是:
Dockerfile
源码
Composer
Node.js
Git
CI 环境负责:
Source Code
↓
Dockerfile
↓
Image
服务器负责:
Registry
↓
Image
↓
Container
这就是容器化交付的重要价值。
16. 推荐的生产发布方式
开发环境可以:
docker build -t my-app:latest .
但是生产环境建议使用明确版本:
my-app:1.0.0
my-app:1.0.1
my-app:1.1.0
甚至可以使用 Git Commit:
my-app:8f3a91c
或者:
my-app:20260915-8f3a91c
例如:
docker buildx build \
--platform linux/amd64 \
-t registry.example.com/team/my-app:20260915-8f3a91c \
--push \
.
服务器:
docker pull \
registry.example.com/team/my-app:20260915-8f3a91c
然后部署这个确定版本。
这样可以实现:
v1.0.0
↓
部署
↓
出现问题
↓
回滚
↓
v0.9.9
而不是:
latest
↓
到底是哪一个版本?
对于生产系统来说,可追踪、可复现、可回滚比单纯使用 latest 更重要。
17. 总结
Docker 镜像的完整生命周期可以归纳为:
Dockerfile
│
▼
docker build
│
▼
Image
│
docker tag
│
▼
Registry Image Name
│
docker login
│
docker push
│
▼
Container Registry
│
docker pull
│
▼
Production
│
docker run
│
▼
Container
日常开发最应该掌握的命令:
# 构建
docker build -t my-app:1.0.0 .
# 查看镜像
docker images
# Tag
docker tag \
my-app:1.0.0 \
registry.example.com/team/my-app:1.0.0
# 登录
docker login registry.example.com
# Push
docker push \
registry.example.com/team/my-app:1.0.0
# Pull
docker pull \
registry.example.com/team/my-app:1.0.0
进一步进入 CI/CD 后,可以直接使用:
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t registry.example.com/team/my-app:1.0.0 \
--push \
.
最终形成:
代码
↓
Dockerfile
↓
Build
↓
Image
↓
Tag
↓
Push
↓
Registry
↓
Pull
↓
Deploy
这条链路实际上就是 Docker 从「本地容器工具」进入「标准化应用交付体系」的核心过程。