Dockerfile的一些用法和最佳实践记录

记录一些在使用Dockerfile过程中遇到的用法和最佳实践。

COPY 和 ADD 命令的区别

COPYADD 都是 Dockerfile 的指令,都可以将文件或目录从主机复制到 docker 镜像中。但是,它们之间存在一些区别:

  1. 功能: COPY 指令将从构建上下文中复制新的文件或目录,并将它们添加到镜像的文件系统中指定的路径。 ADD 指令也有类似的功能,但是它还有两个额外的功能。首先,如果源文件是一个 tar 文件,ADD 将自动解压这个 tar 文件。其次,ADD 指令支持使用 URL 作为源文件,会自动下载这个 URL 指向的文件。

  2. 使用建议: 由于 ADD 指令具有更多的功能,所以它的行为也更复杂,更不可预测。 Docker 官方建议,只有在你确实需要 ADD 提供的额外功能时才使用它,否则默认使用 COPY 指令。这样可以使 Dockerfile 更易于理解,更具可维护性。

  3. 示例:

    • 使用 COPY 指令:
    1
    COPY test.txt /data/

    将当前目录下的 test.txt 文件复制到镜像的 /data/ 目录下。

    • 使用 ADD 指令:
    1
    ADD https://example.com/test.txt /data/

    将远程的 test.txt 文件下载并复制到镜像的 /data/ 目录下。

多阶段构建

Dockerfile的多阶段编译是Docker 17.05版本以后引入的一种新特性,它可以让你在一个Dockerfile中使用多个FROM指令。每个FROM指令可以使用不同的基础镜像,并且开始一个新的构建阶段。每个阶段是完全独立的,可以被认为是一个临时的中间镜像。

多阶段构建的优点主要有两个:一是可以避免最终生产的Docker镜像变得过大;二是可以避免在构建过程中在镜像中留下不必要的工具和依赖。

以下是一个使用多阶段构建的例子,它首先使用golang镜像来编译Go应用程序,然后在新的阶段使用基于alpine的较小镜像来运行该应用程序:

1
2
3
4
5
6
7
8
9
10
11
12
# 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和ENTRYPOINT有什么区别

CMD 设置默认的被容器执行的命令,并且可以有参数。如果 Docker 运行时(也就是docker run命令)指定了其他命令,CMD 命令会被忽略。

ENTRYPOINT 配置容器启动时运行的命令,让容器以应用程序或服务的形式运行。不同于 CMD,它不会被 docker run 的命令行参数覆盖

也正是这个原因,一般来说,推荐使用ENTRYPOINT, 把所有需要的执行的命令都写进一个脚本,这样可以减少上线过程中的由于传参导致的问题。

如何获取一个docker image的SHA256

1
docker inspect --format='{{index .RepoDigests 0}}' <docker image> | cut -d ':' -f 2

Docker Compose

Docker Compose 是一款用于定义和运行多容器 Docker 应用程序的工具,它允许用户通过一个 YAML 文件(通常名为 docker-compose.yml)来配置整个应用的容器服务、网络、数据卷以及其他相关设置。Docker Compose 是 Docker 官方提供的编排工具,主要用于简化在单台机器上运行多个 Docker 容器的过程。

从我的实际工作经验来看,docker compose最大的好处有两个:依赖管理和环境切换。
Docker Compose 可以管理服务间的依赖关系,确保服务按照正确的顺序启动和停止。
也可以为不同的环境(如开发、测试、生产)编写不同的 docker-compose.yml 文件,并通过 -f 参数指定加载不同的配置文件。

下面是一个案例和讲解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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_HOSTdb,这是因为在同一个 Docker Compose 网络中,服务可以通过服务名进行互相访问。
  • frontend 服务同样依赖于 backend 服务,只有当 backend 完全启动后才会启动 frontend 服务。
  • volumes 部分定义了一个持久化的数据卷 db_data,用于存储 db 服务的数据,确保数据在容器重启时不丢失。
  • 最后,所有的服务都被分配到了默认网络 my_app_net 中,以便它们之间能够相互通信。