Docker Build 命令详解:从入门到实战
在现代软件开发和部署流程中,Docker 已经成为不可或缺的工具。它通过容器化技术,极大地简化了应用的打包、分发和运行。而构建 Docker 镜像,是将你的应用及其所有依赖打包成可移植单元的第一步,也是最核心的一步。docker build
命令正是负责执行这一关键任务的工具。
本文将带您深入了解 docker build
命令,从基础概念到高级用法,再结合实战示例,助您彻底掌握 Docker 镜像的构建过程。
1. Docker 镜像与 Dockerfile 基础概念
在深入 docker build
命令之前,我们先来理解几个核心概念:
- Docker 镜像 (Image): Docker 镜像是一个轻量级、独立、可执行的软件包,包含运行一个特定应用所需的一切:代码、运行时环境、库、环境变量和配置文件等。镜像可以看作是一个只读的模板。
- 容器 (Container): 容器是 Docker 镜像的一个运行实例。一个镜像可以创建多个容器。容器是可读写的,它们在镜像层之上添加了一个薄的读写层。
- Dockerfile: Dockerfile 是一个文本文件,其中包含了一系列指令,这些指令描述了如何一步步构建一个 Docker 镜像。
docker build
命令读取 Dockerfile 中的指令并执行,从而自动化地创建镜像。
简单来说,Dockerfile 是构建镜像的“菜谱”,docker build
命令就是根据这个“菜谱”来“烹饪”镜像。
2. docker build
命令入门
docker build
命令的基本语法如下:
bash
docker build [OPTIONS] PATH | URL | -
这个命令有三个主要部分:
docker build
: 命令本身。[OPTIONS]
: 可选参数,用于控制构建过程,比如指定标签、使用缓存、构建参数等。PATH | URL | -
: 构建上下文 (Build Context)。这是构建命令的必需参数,它指定了 Dockerfile 所在的目录(路径)、一个 Git 仓库的 URL 或一个 Tarball 的 URL。或者使用-
从标准输入读取 Dockerfile。
2.1 构建上下文 (Build Context)
理解构建上下文是使用 docker build
的关键。构建上下文是指本地文件系统中的一个目录(通常是当前目录 .
),或者一个可以通过 URL 访问到的资源。在构建过程中,Docker Daemon(Docker守护进程)会将整个构建上下文的内容发送给它。Dockerfile 中的 COPY
和 ADD
等指令就是从这个上下文中复制文件到镜像中。
重要提示: 将整个构建上下文发送给 Docker Daemon 可能包含大量不需要的文件,这会减慢构建速度并占用不必要的资源。因此,建议将构建上下文设置为仅包含Dockerfile和构建所需文件的最小目录。使用 .dockerignore
文件可以进一步排除不需要的文件(类似于 Git 的 .gitignore
)。
示例:
假设你的项目结构如下:
my-app/
├── Dockerfile
├── app.py
├── requirements.txt
└── static/
└── index.html
你在 my-app
目录下执行构建命令:
bash
docker build .
这里的 .
就代表当前目录 my-app
作为构建上下文。Docker Daemon 会将 my-app
目录下的所有文件(除了 .dockerignore
中指定的)都发送过去。Dockerfile 中的 COPY app.py /app/
才能找到并复制 app.py
文件。
2.2 第一个 Dockerfile 和构建命令
让我们创建一个最简单的 Dockerfile 来构建一个基于 Alpine Linux 的镜像:
“`dockerfile
Dockerfile
FROM alpine:latest
RUN echo “Hello, Docker Build!”
“`
将上面的内容保存为名为 Dockerfile
的文件,放在一个空目录中。然后在这个目录下打开终端,执行构建命令:
bash
docker build .
执行后,你会看到类似以下的输出:
Sending build context to Docker daemon 2.048kB
Step 1/2 : FROM alpine:latest
---> a24bb4013296
Step 2/2 : RUN echo "Hello, Docker Build!"
---> Running in a0a1c2d3e4f5
Removing intermediate container a0a1c2d3e4f5
---> b1c2d3e4f5a6
Successfully built b1c2d3e4f5a6
Sending build context...
: Docker Daemon 正在接收构建上下文。Step 1/2 : FROM alpine:latest
: 执行 Dockerfile 中的第一条指令FROM
。它从 Docker Hub 下载alpine:latest
镜像(如果本地没有的话)。---> a24bb4013296
: 这一步构建完成,生成了一个中间镜像层,ID 是a24bb4013296
。Step 2/2 : RUN echo "Hello, Docker Build!"
: 在上一步生成的镜像层之上,执行RUN
指令。Running in ...
显示了 Docker 为执行RUN
命令创建了一个临时容器。Removing intermediate container ...
:RUN
命令执行完成后,临时容器被删除。---> b1c2d3e4f5a6
: 这一步也构建完成,生成了新的镜像层,ID 是b1c2d3e4f5a6
。这个 ID 就是最终构建好的镜像的 ID。Successfully built b1c2d3e4f5a6
: 构建成功,并显示了最终镜像的 ID。
此时,你可以使用 docker images
命令查看本地镜像列表,会看到刚刚构建的镜像(它没有仓库名和标签,只有 Image ID)。
2.3 给镜像打标签 (-t
)
直接使用 Image ID 来引用镜像很不方便。通常我们会给镜像打上一个人类可读的标签。使用 -t
或 --tag
选项可以实现这一点。标签的格式通常是 [仓库名/][镜像名][:标签]
,其中仓库名和标签都是可选的。
bash
docker build -t my-alpine-app:v1.0 .
现在,再次运行 docker images
,你会看到一个名为 my-alpine-app
、标签为 v1.0
的镜像。
你也可以在一次构建中打多个标签:
bash
docker build -t my-alpine-app:v1.0 -t my-alpine-app:latest .
这样,同一个镜像会同时拥有 v1.0
和 latest
两个标签。
3. 深入 Dockerfile 指令
Dockerfile 是构建镜像的核心,理解并正确使用 Dockerfile 中的各种指令至关重要。以下是一些最常用和重要的指令:
-
FROM <image> [AS <name>]
:- 用途: 指定构建过程所基于的父镜像(Base Image)。Dockerfile 中的第一条非注释指令必须是
FROM
。 - 说明: 父镜像可以是任何有效的 Docker 镜像,通常从 Docker Hub 获取。
AS <name>
用于给当前阶段命名,这在多阶段构建中非常有用。 - 示例:
dockerfile
FROM ubuntu:20.04
FROM python:3.9-slim AS builder
- 用途: 指定构建过程所基于的父镜像(Base Image)。Dockerfile 中的第一条非注释指令必须是
-
RUN <command>
:- 用途: 在当前镜像层之上执行任何命令,并在执行后提交结果生成新的镜像层。常用于安装软件包、创建目录、编译应用等。
- 语法:
- Shell 形式:
RUN command param1 param2
(命令在/bin/sh -c
或等效的 shell 中运行) - Exec 形式:
RUN ["executable", "param1", "param2"]
(直接执行可执行文件)
- Shell 形式:
- 最佳实践: 为了减少镜像层数和镜像大小,尽量将多个
RUN
命令合并成一个,并使用&&
连接,同时注意清理不需要的临时文件(如包管理器的缓存)。 - 示例:
dockerfile
RUN apt-get update && apt-get install -y some-package && rm -rf /var/lib/apt/lists/*
RUN ["/bin/bash", "-c", "echo hello"]
-
COPY <src>... <dest>
/COPY ["<src>",... "<dest>"]
:- 用途: 从构建上下文目录中复制文件或目录到镜像的文件系统中指定路径。
- 说明:
src
是构建上下文中的路径,dest
是镜像中的绝对路径或相对于WORKDIR
的路径。如果dest
不存在,会自动创建。 - 与
ADD
的区别:COPY
仅支持本地文件复制;ADD
除了复制本地文件,还支持解压 Tarball 和从 URL 下载文件。通常推荐使用COPY
,因为它更明确,不易产生意外行为。 - 示例:
dockerfile
COPY app.py /app/
COPY ./src /app/src
COPY requirements.txt . # 复制到当前 WORKDIR
-
ADD <src>... <dest>
/ADD ["<src>",... "<dest>"]
:- 用途: 功能类似于
COPY
,但额外支持以下功能:src
可以是一个 URL。- 如果
src
是一个本地的 Tar 压缩文件,Docker 会自动将其解压到dest
。
- 最佳实践: 尽量使用
COPY
,只有在需要自动解压本地 Tar 文件或从 URL 下载时才考虑使用ADD
。 - 示例:
dockerfile
ADD https://example.com/somefile.tar.gz /tmp/ # 下载并自动解压
ADD myarchive.tar.gz /app/ # 解压本地 Tar 到 /app
- 用途: 功能类似于
-
WORKDIR /path/to/workdir
:- 用途: 设置后续
RUN
,CMD
,ENTRYPOINT
,COPY
,ADD
等指令执行时的当前工作目录。如果目录不存在,会自动创建。 - 说明: 建议在 Dockerfile 中明确设置工作目录,而不是依赖默认值。可以使用多个
WORKDIR
指令,后续指令会在前一个WORKDIR
设置的基础上进行相对路径操作。 - 示例:
dockerfile
WORKDIR /app
COPY . . # 复制构建上下文内容到 /app
RUN echo "Current dir: $(pwd)" # 会输出 /app
- 用途: 设置后续
-
CMD <command>
/CMD ["executable","param1","param2"]
/CMD ["param1","param2"]
(作为 ENTRYPOINT 的参数):- 用途: 设置容器启动时默认执行的命令。一个 Dockerfile 只能有一个
CMD
,如果有多个,只有最后一个生效。 - 语法:
- Shell 形式:
CMD command param1 param2
(在 shell 中运行,适用于执行带有变量或需要 shell 特性的命令) - Exec 形式:
CMD ["executable", "param1", "param2"]
(首选,直接执行可执行文件) - Default Parameters 形式:
CMD ["param1", "param2"]
(用于为ENTRYPOINT
指令提供默认参数)
- Shell 形式:
- 与
ENTRYPOINT
的区别:CMD
可以在docker run
命令中被覆盖(docker run <image> <new_command>
);ENTRYPOINT
通常不易被覆盖,它的作用是使容器像一个可执行程序一样运行。如果同时定义了ENTRYPOINT
和CMD
,CMD
的内容会作为参数传递给ENTRYPOINT
。 - 示例:
dockerfile
CMD ["python", "app.py"] # Exec 形式,推荐
CMD python app.py # Shell 形式
CMD ["--help"] # 作为 ENTRYPOINT ["my-app"] 的默认参数
- 用途: 设置容器启动时默认执行的命令。一个 Dockerfile 只能有一个
-
ENTRYPOINT <command>
/ENTRYPOINT ["executable", "param1", "param2"]
:- 用途: 配置一个作为容器主命令执行的命令。与
CMD
类似,但通常用作容器的可执行入口。一个 Dockerfile 只能有一个ENTRYPOINT
。 - 语法: 同
CMD
。 - 用途: 常用于创建一个可执行的容器。当与
CMD
结合使用时,CMD
提供默认参数,docker run
命令行提供的参数会覆盖CMD
的默认参数,然后这些参数一起传递给ENTRYPOINT
。 - 示例:
dockerfile
ENTRYPOINT ["/app/run.sh"]
# 如果 CMD 为 ["--help"],运行容器时默认执行 /app/run.sh --help
# 如果运行 docker run my-image --version,则执行 /app/run.sh --version
- 用途: 配置一个作为容器主命令执行的命令。与
-
ENV <key>=<value> ...
:- 用途: 设置环境变量。这些环境变量在构建过程中(在
RUN
指令中可以使用)和容器运行时都有效。 - 说明: 可以一次设置多个环境变量。
- 示例:
dockerfile
ENV APP_HOME=/app
ENV PATH=$APP_HOME/bin:$PATH VERSION=1.0
- 用途: 设置环境变量。这些环境变量在构建过程中(在
-
EXPOSE <port> [<port>/<protocol>...]
:- 用途: 声明容器运行时监听的端口。这仅是一个文档说明,告诉用户容器暴露了哪些端口。要实际映射端口,需要在
docker run
命令中使用-p
选项。 - 示例:
dockerfile
EXPOSE 80
EXPOSE 8000/tcp 5432/udp
- 用途: 声明容器运行时监听的端口。这仅是一个文档说明,告诉用户容器暴露了哪些端口。要实际映射端口,需要在
-
VOLUME <mountpoint>
/VOLUME ["<mountpoint>", ...]
:- 用途: 创建一个挂载点,可以将主机目录或其它容器的数据卷挂载到容器中。这用于持久化数据或共享数据。
- 说明: 仅声明容器内部的挂载点,实际的卷创建或绑定挂载需要在
docker run
命令中使用-v
或--mount
选项。 - 示例:
dockerfile
VOLUME /data
VOLUME ["/app/logs", "/app/config"]
-
USER <user>[:<group>]
:- 用途: 设置后续
RUN
,CMD
,ENTRYPOINT
指令执行时的用户和用户组。默认是root
。 - 最佳实践: 为了安全起见,除非必要,否则不要使用
root
用户运行容器内的进程。可以创建一个非特权用户并在USER
指令中指定。 - 示例:
dockerfile
RUN groupadd -r myuser && useradd -r -g myuser myuser
USER myuser
CMD ["/app/run_as_user.sh"]
- 用途: 设置后续
-
ARG <name>[=<default value>]
:- 用途: 定义一个构建参数 (Build Argument),可以在
docker build
命令中使用--build-arg <name>=<value>
选项来传递值。这些值在构建过程中可用(例如在RUN
指令中),但在容器运行时不可用(除非使用ENV
指令将ARG
的值显式设置为环境变量)。 - 与
ENV
的区别:ARG
仅在构建时有效,ENV
在构建时和运行时都有效。 - 示例:
dockerfile
ARG VERSION=latest
ARG BUILD_DATE
ENV APP_VERSION=$VERSION # 将构建参数转换为环境变量
RUN echo "Building version $VERSION built on $BUILD_DATE" - 构建命令:
docker build --build-arg VERSION=v1.1 --build-arg BUILD_DATE=$(date +%F) -t my-app .
- 用途: 定义一个构建参数 (Build Argument),可以在
-
LABEL <key>="<value>" [<key>="<value>" ...]
:- 用途: 为镜像添加元数据(metadata)。可以用于组织镜像、过滤镜像、提供信息等。
- 示例:
dockerfile
LABEL maintainer="Your Name <[email protected]>"
LABEL version="1.0"
LABEL description="This is a sample web application image."
-
HEALTHCHECK [OPTIONS] CMD command
/HEALTHCHECK NONE
:- 用途: 配置容器的健康检查。Docker 可以定期执行一个命令来检查容器是否健康运行。
- 选项:
--interval
,--timeout
,--start-period
,--retries
。 - 示例:
dockerfile
HEALTHCHECK --interval=5m --timeout=3s \
CMD curl -f http://localhost/ || exit 1
-
SHELL ["executable", "parameters"]
:- 用途: 覆盖用于
RUN
,CMD
,ENTRYPOINT
的 shell 形式指令的默认 shell。默认是["/bin/sh", "-c"]
(Linux) 或["cmd", "/S", "/C"]
(Windows)。 - 示例:
dockerfile
SHELL ["/bin/bash", "-c"]
RUN echo "Hello, world!" # 会在 /bin/bash -c "echo \"Hello, world!\"" 中运行
- 用途: 覆盖用于
-
STOPSIGNAL signal
:- 用途: 设置发送给容器以退出的系统调用信号。默认为
SIGTERM
。 - 示例:
dockerfile
STOPSIGNAL SIGKILL
- 用途: 设置发送给容器以退出的系统调用信号。默认为
-
ONBUILD <instruction>
:- 用途: 定义一个触发器指令。当当前镜像被用作另一个镜像的
FROM
父镜像时,ONBUILD
后面的指令将在子镜像构建时执行。 - 示例:
dockerfile
ONBUILD COPY . /app/src
ONBUILD RUN echo "This runs in the derived image"
当另一个 DockerfileFROM your-image-with-onbuild
时,COPY . /app/src
和RUN echo ...
会在子镜像构建的第一步执行。
- 用途: 定义一个触发器指令。当当前镜像被用作另一个镜像的
4. Docker Build 的高级特性与最佳实践
4.1 利用构建缓存
Docker Build 具有强大的构建缓存机制。Docker Daemon 会将每个成功执行的 Dockerfile 指令的结果作为一个镜像层缓存起来。下次构建时,如果遇到完全相同的指令,并且该指令所依赖的文件(对于 COPY
/ADD
)没有变化,Docker 会直接使用缓存的镜像层,而不是重新执行该指令。这极大地加速了构建过程。
缓存失效规则:
FROM
指令:如果父镜像发生变化(例如alpine:latest
更新了),缓存就会失效。RUN
指令:如果指令字符串完全一样,则命中缓存。COPY
/ADD
指令:Docker 会检查源文件和目标文件的内容(校验和)和元数据(修改时间等)。如果源文件或目标文件发生变化,缓存就会失效。- 指令顺序:如果 Dockerfile 中的指令顺序发生变化,缓存也会失效。一旦某个指令没有命中缓存,其后的所有指令的缓存都会失效,需要重新执行。
利用缓存的技巧:
- 将 Dockerfile 中不经常变化的指令放在前面(如
FROM
,RUN apt-get update
)。 - 将经常变化的指令放在后面(如
COPY app.py
)。 -
对于 Node.js, Python 等项目,先复制依赖文件 (
package.json
,requirements.txt
),然后安装依赖,最后复制应用代码。这样,如果只有应用代码变动而依赖不变,安装依赖的步骤可以利用缓存:“`dockerfile
Example: Python app
FROM python:3.9-slim
WORKDIR /app
COPY requirements.txt . # Step 1: Copy dependency file
RUN pip install -r requirements.txt # Step 2: Install dependencies (cache friendly if requirements.txt doesn’t change)
COPY . . # Step 3: Copy application code (this step invalidates cache if code changes)
CMD [“python”, “app.py”]
“`
强制禁用缓存:
使用 --no-cache
选项可以完全禁用构建缓存,强制重新执行所有指令:
bash
docker build --no-cache -t my-app:latest .
这在排除构建问题时可能有用,但会显著增加构建时间。
4.2 .dockerignore
文件
为了优化构建上下文,减少发送到 Docker Daemon 的文件数量,可以使用 .dockerignore
文件。该文件的语法类似于 .gitignore
,用于指定在构建上下文中要忽略的文件和目录。
示例 .dockerignore
文件:
.git
.gitignore
node_modules/
tmp/
*.log
这样,在构建时,Docker Daemon 不会将 .git
目录、node_modules
目录等发送到构建上下文。
4.3 多阶段构建 (Multi-stage Builds)
多阶段构建是构建高效、小型镜像的强大技术。它允许你在一个 Dockerfile 中使用多个 FROM
指令,每个 FROM
指令定义一个独立的构建阶段。你可以在前一个阶段编译、测试或构建应用,然后只将最终生成的、可执行的 artifact 复制到下一个(通常是最终的)轻量级运行时镜像中。中间阶段及其包含的所有构建依赖(编译器、SDK、临时文件等)都不会包含在最终镜像中,从而大大减小镜像体积和攻击面。
语法:
使用 FROM <image> AS <stage-name>
给阶段命名。然后使用 COPY --from=<stage-name>
或 COPY --from=<stage-index>
从前一个阶段复制文件。
示例 (Go 应用):
“`dockerfile
Stage 1: Builder
FROM golang:1.18 AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY *.go .
RUN go build -o /app/my-app
Stage 2: Runner
FROM alpine:latest
WORKDIR /app
COPY –from=builder /app/my-app . # Copy the built binary from the builder stage
CMD [“./my-app”]
“`
在这个例子中,第一阶段 (builder
) 包含了 Go SDK 和所有构建依赖。第二阶段 (alpine
) 是一个非常小的基础镜像。最终的镜像只包含从 builder
阶段复制过来的编译好的 my-app
可执行文件,而不包含 Go SDK 和源码,体积会非常小。
使用 docker build .
构建时,Docker 会执行所有阶段,但最终只生成最后一个阶段的镜像。你也可以使用 --target <stage-name>
选项来构建到指定阶段,这在调试构建过程时很有用。
bash
docker build --target builder -t my-app-builder . # 只构建 builder 阶段
4.4 BuildKit (下一代构建引擎)
BuildKit 是 Docker 提供的一个更现代、更强大、更安全的镜像构建引擎。它带来了许多改进,包括:
- 并行构建: 可以并行执行 Dockerfile 中的独立指令,显著提升构建速度。
- 跳过未使用的阶段: 对于多阶段构建,如果未使用
--target
指定,BuildKit 会自动跳过最终镜像不需要的阶段,进一步加快构建。 - 新的构建特性: 支持 Secrets(安全地将敏感信息传递给构建过程)、SSH mounts(在构建过程中通过 SSH 访问私有仓库)、Cache mounts(在不同构建之间共享缓存目录)等。
- 更好的缓存管理。
- 更详细的构建输出。
启用 BuildKit:
- 环境变量: 在执行
docker build
命令前设置DOCKER_BUILDKIT=1
。
bash
DOCKER_BUILDKIT=1 docker build -t my-app . - Daemon 配置: 在
/etc/docker/daemon.json
中添加"features": {"buildkit": true}
并重启 Docker Daemon(生产环境推荐)。
示例: 使用 BuildKit Secrets (假设有一个 secret 文件 my-token
):
“`dockerfile
syntax=docker/dockerfile:1.2 # Specify BuildKit frontend
FROM alpine:latest
RUN –mount=type=secret,id=mysecret cat /run/secrets/mysecret
“`
构建命令 (假设你的 secret 文件在 /path/to/my-token
):
bash
DOCKER_BUILDKIT=1 docker build --secret id=mysecret,src=/path/to/my-token -t secret-builder .
这样,my-token
文件的内容就会安全地传递到构建过程中的 /run/secrets/mysecret
,不会残留在镜像层中。
4.5 使用构建参数 --build-arg
前面提到 ARG
指令定义构建参数。使用 --build-arg <name>=<value>
选项可以在构建时为这些参数赋值。这使得构建过程更加灵活,可以根据需要构建不同版本或配置的镜像。
示例:
Dockerfile:
dockerfile
FROM ubuntu:latest
ARG VERSION=1.0
RUN echo "Building version: $VERSION"
ENV APP_VERSION=$VERSION
构建命令:
bash
docker build --build-arg VERSION=2.5 -t my-app:2.5 .
docker build -t my-app:latest . # 使用默认值 1.0
5. docker build
实战示例
5.1 构建一个简单的 Node.js Web 应用
假设你的 Node.js 应用结构如下:
nodejs-app/
├── Dockerfile
├── package.json
├── package-lock.json
└── server.js
package.json
:
json
{
"name": "simple-node-app",
"version": "1.0.0",
"description": "A simple Node.js web app",
"main": "server.js",
"scripts": {
"start": "node server.js"
},
"dependencies": {
"express": "^4.17.1"
}
}
server.js
:
“`javascript
const express = require(‘express’);
const app = express();
const port = 3000;
app.get(‘/’, (req, res) => {
res.send(‘Hello from Dockerized Node.js App!’);
});
app.listen(port, () => {
console.log(App listening at http://localhost:${port}
);
});
“`
Dockerfile (nodejs-app/Dockerfile
):
“`dockerfile
Use a multi-stage build for smaller image size
Stage 1: Builder – Install dependencies
FROM node:18-alpine AS builder
WORKDIR /app
Copy package.json and package-lock.json first to leverage cache
COPY package*.json ./
Install dependencies
RUN npm ci
Copy the rest of the application code
COPY . .
Stage 2: Runner – Minimal runtime image
FROM node:18-alpine
WORKDIR /app
Copy only node_modules and the app code from the builder stage
COPY –from=builder /app/node_modules ./node_modules
COPY –from=builder /app/server.js .
Expose the port the app runs on
EXPOSE 3000
Define the command to run the application
CMD [“node”, “server.js”]
“`
构建命令 (在 nodejs-app
目录下):
bash
docker build -t simple-node-app:latest .
解释:
- Stage 1 (
builder
):- 使用
node:18-alpine
作为基础镜像,它包含了 Node.js 运行时和 npm。 - 设置工作目录为
/app
。 - 先
COPY package*.json ./
:这是为了利用缓存。如果package.json
或package-lock.json
没有变化,即使其他应用代码变化了,npm ci
这一步也可以直接使用缓存。 RUN npm ci
: 安装项目依赖。使用npm ci
比npm install
更适合 CI/CD 环境,它会基于package-lock.json
精确安装依赖,并且速度更快。COPY . .
: 复制剩余的所有应用代码到/app
。
- 使用
- Stage 2 (
runner
):- 再次使用
node:18-alpine
作为基础镜像。注意这里没有安装任何构建工具或开发依赖。 - 设置工作目录为
/app
。 COPY --from=builder /app/node_modules ./node_modules
: 从builder
阶段的/app/node_modules
复制已经安装好的依赖到当前阶段。COPY --from=builder /app/server.js .
: 从builder
阶段复制主应用文件。EXPOSE 3000
: 声明容器将监听 3000 端口。CMD ["node", "server.js"]
: 设置容器启动时执行的命令。
- 再次使用
这样构建出的 simple-node-app:latest
镜像将只包含运行应用所需的 Node.js 运行时、node_modules
目录和 server.js
文件,而不会包含构建阶段产生的其他临时文件和工具,体积会小得多。
构建完成后,你可以运行容器:
bash
docker run -d -p 3000:3000 simple-node-app:latest
然后访问 http://localhost:3000
即可看到 “Hello from Dockerized Node.js App!”。
5.2 使用 .dockerignore
优化构建
在上面的 Node.js 示例中,如果你有 node_modules
目录在本地,并且没有使用 .dockerignore
,Docker 会尝试将其也复制到构建上下文中。这会浪费带宽和时间,因为你已经在 Dockerfile 中通过 npm ci
安装了依赖。
在 nodejs-app
目录下创建 .dockerignore
文件:
node_modules/
npm-debug.log
Dockerfile
.git
.gitignore
*.swp
现在再次构建,你会发现 “Sending build context to Docker daemon” 这一步发送的数据量变小了。
6. 进一步探索的选项
除了上述内容,docker build
还有一些其他有用的选项:
-f /path/to/Dockerfile
: 指定非默认名称或位置的 Dockerfile 文件。
bash
docker build -f dockerfiles/prod.Dockerfile -t my-app:prod .--iidfile /path/to/imageid.file
: 将构建完成后镜像的 ID 写入指定文件。--squash
: (注意:此选项有一些限制和潜在问题,且在某些场景下不推荐,多阶段构建通常是更好的选择)尝试将新的文件系统层压缩为单个新层。--platform linux/amd64
等:指定构建目标平台(当使用 Buildx 或 BuildKit 时)。
7. 总结
docker build
命令是 Docker 工作流程的核心。通过学习如何编写高效的 Dockerfile,并结合构建上下文、缓存机制、.dockerignore
文件、多阶段构建以及 BuildKit 等高级特性,您可以创建出体积更小、构建更快、更安全可靠的 Docker 镜像。
从基础的 FROM
和 RUN
到高级的多阶段构建和 BuildKit 特性,本文为您提供了 docker build
的全面指南。掌握了这些知识,您就能更好地将您的应用打包成可移植的 Docker 镜像,为后续的容器化部署打下坚实的基础。
持续实践和探索,结合您的具体应用场景,您将能更有效地利用 Docker 的强大功能。