记录一些在使用Dockerfile过程中遇到的用法和最佳实践。
COPY
和 ADD
都是 Dockerfile 的指令,都可以将文件或目录从主机复制到 docker 镜像中。但是,它们之间存在一些区别:
功能: COPY
指令将从构建上下文中复制新的文件或目录,并将它们添加到镜像的文件系统中指定的路径。 ADD
指令也有类似的功能,但是它还有两个额外的功能。首先,如果源文件是一个 tar 文件,ADD
将自动解压这个 tar 文件。其次,ADD
指令支持使用 URL 作为源文件,会自动下载这个 URL 指向的文件。
使用建议: 由于 ADD
指令具有更多的功能,所以它的行为也更复杂,更不可预测。 Docker 官方建议,只有在你确实需要 ADD
提供的额外功能时才使用它,否则默认使用 COPY
指令。这样可以使 Dockerfile 更易于理解,更具可维护性。
示例:
COPY
指令:COPY test.txt /data/
将当前目录下的 test.txt 文件复制到镜像的 /data/ 目录下。
ADD
指令:ADD https://example.com/test.txt /data/
将远程的 test.txt 文件下载并复制到镜像的 /data/ 目录下。
Dockerfile的多阶段编译是Docker 17.05版本以后引入的一种新特性,它可以让你在一个Dockerfile中使用多个FROM指令。每个FROM指令可以使用不同的基础镜像,并且开始一个新的构建阶段。每个阶段是完全独立的,可以被认为是一个临时的中间镜像。
多阶段构建的优点主要有两个:一是可以避免最终生产的Docker镜像变得过大;二是可以避免在构建过程中在镜像中留下不必要的工具和依赖。
以下是一个使用多阶段构建的例子,它首先使用golang
镜像来编译Go应用程序,然后在新的阶段使用基于alpine的较小镜像来运行该应用程序:
# Stage 1: Build the Go binary
FROM golang:1.14.2 as builder
WORKDIR /go/src/app
COPY . .
RUN go get -d -v ./...
RUN CGO_ENABLED=0 GOOS=linux go build -o app .
# Stage 2: Copy the Go binary to an empty Docker image
FROM alpine:latest
WORKDIR /root/
COPY --from=builder /go/src/app/app .
CMD ["./app"]
在这个例子中,首先定义了一个名为builder
的构建阶段,它从golang:1.14.2
镜像开始,将源代码复制到镜像中,然后编译Go应用程序。
然后,开始第二个构建阶段,它从较小的alpine:latest
镜像开始,并从builder
阶段复制编译好的Go应用程序到新镜像中。
这样,最终得到的镜像中只包含了编译好的Go应用程序,而没有包含用于编译的Go编译器等额外的工具和依赖,使得镜像更加轻量化。
CMD
设置默认的被容器执行的命令,并且可以有参数。如果 Docker 运行时(也就是docker run命令)指定了其他命令,CMD 命令会被忽略。
ENTRYPOINT
配置容器启动时运行的命令,让容器以应用程序或服务的形式运行。不同于 CMD,它不会被 docker run 的命令行参数覆盖
也正是这个原因,一般来说,推荐使用ENTRYPOINT
, 把所有需要的执行的命令都写进一个脚本,这样可以减少上线过程中的由于传参导致的问题。
docker inspect --format='{{index .RepoDigests 0}}' <docker image> | cut -d ':' -f 2
Docker Compose 是一款用于定义和运行多容器 Docker 应用程序的工具,它允许用户通过一个 YAML 文件(通常名为 docker-compose.yml
)来配置整个应用的容器服务、网络、数据卷以及其他相关设置。Docker Compose 是 Docker 官方提供的编排工具,主要用于简化在单台机器上运行多个 Docker 容器的过程。
从我的实际工作经验来看,docker compose最大的好处有两个:依赖管理和环境切换。
Docker Compose 可以管理服务间的依赖关系,确保服务按照正确的顺序启动和停止。
也可以为不同的环境(如开发、测试、生产)编写不同的 docker-compose.yml
文件,并通过 -f
参数指定加载不同的配置文件。
下面是一个案例和讲解。
version: '3.9'
services:
db:
image: postgres:13-alpine
environment:
POSTGRES_USER: myuser
POSTGRES_PASSWORD: example
volumes:
- db_data:/var/lib/postgresql/data
backend:
build: ./backend
depends_on:
- db
environment:
DB_HOST: db
DB_PORT: 5432
DB_USER: myuser
DB_PASS: example
frontend:
build: ./frontend
ports:
- "80:80"
depends_on:
- backend
volumes:
db_data:
networks:
default:
name: my_app_net
在这个例子中:
db
的服务,它是基于 Postgres 数据库镜像的容器。backend
服务依赖于 db
服务,意味着 backend
服务会在 db
服务启动并准备就绪后再启动。depends_on
关键字用于表达这种依赖关系。backend
服务需要连接到 db
服务,所以它设置了 DB_HOST
为 db
,这是因为在同一个 Docker Compose 网络中,服务可以通过服务名进行互相访问。frontend
服务同样依赖于 backend
服务,只有当 backend
完全启动后才会启动 frontend
服务。volumes
部分定义了一个持久化的数据卷 db_data
,用于存储 db
服务的数据,确保数据在容器重启时不丢失。my_app_net
中,以便它们之间能够相互通信。