Skip to content

Dockerfile

前言

前面我们介绍了 Docker 的基本概念,以及 Docker 的基本使用方法,从构建一个镜像来看,需要先编写一个 Dockerfile 文件,然后通过 docker build 命令来构建镜像,这一节我们就来学习一下 Dockerfile 的编写。看下写一个 Dockerfile 文件需要哪些内容。需要注意哪些问题。

Dockerfile 文件

一般会使用 docker build 命令来构建镜像,这个命令会根据 Dockerfile 文件来构建镜像。

bash
docker build -t <image_name:tag> -f filename <Dockerfile_path>

TIP

  • image_name:tag 镜像名称和标签
  • filename Dockerfile 文件名称
  • Dockerfile_path Dockerfile 文件路径

一般 filename 都是默认的不需要指定,Dockerfile_path 默认是当前目录,所以一般我们会使用下面的命令来构建镜像。

bash
docker build -t <image_name:tag> .

.dockerignore 文件

在构建镜像的过程中,会将当前目录下的所有文件都打包到镜像中,但是有些文件是不需要打包到镜像中的,比如一些临时文件,或者一些敏感文件,这个时候就需要使用.dockerignore 文件来指定不需要打包到镜像中的文件。

.dockerignore
# 一个忽略文件的例子
*.md
!README.md
node_modules/
[a-c].txt
.git/
.DS_Store
.vscode/
.dockerignore
.eslintignore
.eslintrc
.prettierrc
.prettierignore

看下这个文件的规则:

TIP

  • # 表示注释
  • *.md 表示忽略所有的 md 文件
  • !README.md 表示不忽略 README.md 文件
  • node_modules/ 表示忽略 node_modules 目录
  • [a-c].txt 表示忽略 a.txt、b.txt、c.txt 文件
  • .git/ 表示忽略.git 目录
  • .DS_Store 表示忽略.DS_Store 文件
  • .vscode/ 表示忽略.vscode 目录
  • .dockerignore 表示忽略.dockerignore 文件
  • .eslintignore 表示忽略.eslintignore 文件
  • .eslintrc 表示忽略.eslintrc 文件
  • .prettierrc 表示忽略.prettierrc 文件
  • .prettierignore 表示忽略.prettierignore 文件

项目根目录中如果有这个忽略文件之后,在执行 docker build 命令的时候,会先解析这个.dockerignore 文件,把符合规则的文件都忽略掉,然后再打包到镜像中。这样就能减小镜像的体积,提高镜像的构建速度。

多阶段构建

为什么会出现多阶段构建呢?在前面的构建中使用忽略文件是为了减少打包的体积,但是有些时候我们需要在构建镜像的时候,需要安装一些依赖,比如安装一些编译工具,或者安装一些依赖包,但是这些依赖包在运行的时候是不需要的,这个时候就需要使用多阶段构建来解决这个问题。 比如就拿一个 nest 应用来举例,当直接构建镜像的时候,会把 src 的目录也打包到镜像中,但是在运行的时候只需要 dist 目录就可以了,这个时候就可以使用多阶段构建来解决这个问题。

dockerfile
# 第一阶段
FROM node:18 as build-stage

# 工作目录
WORKDIR /app

# 复制依赖文件
COPY package.json .

# 执行设置淘宝镜像命令
RUN npm config set registry https://registry.npmmirror.com/

# 安装依赖
RUN npm install

# 复制所有文件
COPY . .

# 执行构建命令
RUN npm run build

# 第二阶段
FROM node:18 as production-stage

# 复制第一阶段的文件构建好的dist目录到第二阶段 app目录
COPY --from=build-stage /app/dist /app

# 复制第一阶段的 package.json 文件
COPY --from=build-stage /app/package.json /app/package.json

# 设置工作目录
WORKDIR /app

# 安装依赖
RUN npm install --production

# 暴露端口
EXPOSE 3000

# 启动命令
CMD ["node", "/app/main.js"]

然后构建下这个镜像,会发现镜像的体积变小了。

bash
docker build -t nest-app .

但是,这里的体积变小仅仅是没有去除了一些在开发环境运行时候不需要的文件,但是其实在构建的时候还可以继续优化镜像的大小,因为基础镜像有专门的轻量化版本,比如 node:18-alpine,这个镜像就比 node:18 要小很多,所以我们可以使用这个镜像来构建镜像,这样就能进一步减小镜像的体积。

dockerfile
# 使用轻量化基础镜像
FROM node:18.0-alpine3.14 as build-stage

WORKDIR /app

COPY package.json .

RUN npm install

COPY . .

RUN npm run build

# production stage
FROM node:18.0-alpine3.14 as production-stage

COPY --from=build-stage /app/dist /app
COPY --from=build-stage /app/package.json /app/package.json

WORKDIR /app

RUN npm install --production

EXPOSE 3000

CMD ["node", "/app/main.js"]

然后构建下这个版本的镜像,会发现镜像的体积又变小了。

bash
docker build -t nest-app .

编写 dockerfile 的一些技巧

在上面的例子中,其实已经使用了一些技巧,比如用来减少构建的时间和镜像的体积,但是其实还有一些其他的技巧,这里总体介绍下

使用 alpine 版本的基础镜像

这个在上面的例子中已经介绍过了,使用 alpine 版本的基础镜像,能减小镜像的体积。其实 alpine 版本只是去掉了一个 linux 中用不到的功能,比如去掉了一些命令,去掉了一些库,所以在构建的时候,如果不需要这些功能,就可以使用 alpine 版本的基础镜像。

使用多阶段构建

使用多阶段构建,能减小镜像的体积,因为在构建的时候,只需要把运行时需要的文件打包到镜像中就可以了,而不需要把开发时候的文件也打包到镜像中。

使用 .dockerignore 文件

使用 .dockerignore 文件,能减小镜像的体积,因为在构建的时候,会先解析这个文件,把符合规则的文件都忽略掉,然后再打包到镜像中。这样就能减小镜像的体积,提高镜像的构建速度。

使用使用 ARG 增加构建灵活性

使用 ARG 增加构建灵活性,因为在构建的时候,可以通过传入参数来构建不同的镜像,比如可以通过传入不同的参数来构建不同的环境的镜像,比如开发环境的镜像、测试环境的镜像、生产环境的镜像。

dockerfile
FROM node:18.0-alpine3.14 as build-stage

ARG NODE_ENV

ENV NODE_ENV=${NODE_ENV}

WORKDIR /app

COPY package.json .

RUN npm install

COPY . .

RUN npm run build

# production stage

FROM node:18.0-alpine3.14 as production-stage

COPY --from=build-stage /app/dist /app
COPY --from=build-stage /app/package.json /app/package.json

WORKDIR /app

RUN npm install --production

EXPOSE 3000

CMD ["node", "/app/main.js"]

然后构建下这个版本的镜像,会发现可以动态改变环境变量了。

bash
docker build -t nest-app --build-arg NODE_ENV=production .

CMD 和 ENTRYPOINT 的区别与使用

CMDENTRYPOINT 都是用来指定容器启动时候执行的命令,但是他们之间有一些区别,CMD 是指定容器启动时候执行的命令,而 ENTRYPOINT 是指定容器启动时候执行的命令的入口,也就是说 ENTRYPOINT 指定的命令会作为 CMD 指定的命令的参数,比如下面的例子

dockerfile
FROM node:18.0-alpine3.14 as build-stage


ENTRYPOINT ["echo", "water"]

CMD ["nest-app"]

然后构建下这个版本的镜像,会发现可以动态改变环境变量了。

bash
docker build -t nest-app  .

docker run nest-app
// nest-app

docker run nest-app water
// water

从执行结果可以看出,ENTRYPOINT 如果指定了命令,那么命令行中输入的命令会作为 ENTRYPOINT 指定的命令的额外参数,而 CMD 指定的命令传人的参数会直接覆盖掉。

小结

docker build 的时候会通过 dockerfile 来构建镜像。然后通过 .dockerignore 指定哪些文件不打包进镜像,这样能加快构建时间,减小镜像体积。 此外,多阶段构建也能减小镜像体积,也就是 build 一个镜像、production 一个镜像,最终保留下 production 的镜像。 而且一般使用 alpine 的基础镜像,类似 node:18.10-aline3.14,这样构建出来镜像体积会小很多。运用以上一些优化手段,能构建出体积小、构建速度快的镜像。

如有转载或 CV 的请标注本站原文地址