在前面的文章中,我们已经介绍了 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 从「本地容器工具」进入「标准化应用交付体系」的核心过程。