Dockerfile
前言
前面我们介绍了 Docker
的基本概念,以及 Docker
的基本使用方法,从构建一个镜像来看,需要先编写一个 Dockerfile
文件,然后通过 docker build
命令来构建镜像,这一节我们就来学习一下 Dockerfile
的编写。看下写一个 Dockerfile
文件需要哪些内容。需要注意哪些问题。
Dockerfile 文件
一般会使用 docker build
命令来构建镜像,这个命令会根据 Dockerfile
文件来构建镜像。
docker build -t <image_name:tag> -f filename <Dockerfile_path>
TIP
image_name:tag
镜像名称和标签filename
Dockerfile 文件名称Dockerfile_path
Dockerfile 文件路径
一般 filename
都是默认的不需要指定,Dockerfile_path
默认是当前目录,所以一般我们会使用下面的命令来构建镜像。
docker build -t <image_name:tag> .
.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
目录就可以了,这个时候就可以使用多阶段构建来解决这个问题。
# 第一阶段
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"]
然后构建下这个镜像,会发现镜像的体积变小了。
docker build -t nest-app .
但是,这里的体积变小仅仅是没有去除了一些在开发环境运行时候不需要的文件,但是其实在构建的时候还可以继续优化镜像的大小,因为基础镜像有专门的轻量化版本,比如 node:18-alpine
,这个镜像就比 node:18
要小很多,所以我们可以使用这个镜像来构建镜像,这样就能进一步减小镜像的体积。
# 使用轻量化基础镜像
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"]
然后构建下这个版本的镜像,会发现镜像的体积又变小了。
docker build -t nest-app .
编写 dockerfile 的一些技巧
在上面的例子中,其实已经使用了一些技巧,比如用来减少构建的时间和镜像的体积,但是其实还有一些其他的技巧,这里总体介绍下
使用 alpine 版本的基础镜像
这个在上面的例子中已经介绍过了,使用 alpine
版本的基础镜像,能减小镜像的体积。其实 alpine
版本只是去掉了一个 linux
中用不到的功能,比如去掉了一些命令,去掉了一些库,所以在构建的时候,如果不需要这些功能,就可以使用 alpine
版本的基础镜像。
使用多阶段构建
使用多阶段构建,能减小镜像的体积,因为在构建的时候,只需要把运行时需要的文件打包到镜像中就可以了,而不需要把开发时候的文件也打包到镜像中。
使用 .dockerignore 文件
使用 .dockerignore
文件,能减小镜像的体积,因为在构建的时候,会先解析这个文件,把符合规则的文件都忽略掉,然后再打包到镜像中。这样就能减小镜像的体积,提高镜像的构建速度。
使用使用 ARG 增加构建灵活性
使用 ARG
增加构建灵活性,因为在构建的时候,可以通过传入参数来构建不同的镜像,比如可以通过传入不同的参数来构建不同的环境的镜像,比如开发环境的镜像、测试环境的镜像、生产环境的镜像。
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"]
然后构建下这个版本的镜像,会发现可以动态改变环境变量了。
docker build -t nest-app --build-arg NODE_ENV=production .
CMD 和 ENTRYPOINT 的区别与使用
CMD
和 ENTRYPOINT
都是用来指定容器启动时候执行的命令,但是他们之间有一些区别,CMD
是指定容器启动时候执行的命令,而 ENTRYPOINT
是指定容器启动时候执行的命令的入口,也就是说 ENTRYPOINT
指定的命令会作为 CMD
指定的命令的参数,比如下面的例子
FROM node:18.0-alpine3.14 as build-stage
ENTRYPOINT ["echo", "water"]
CMD ["nest-app"]
然后构建下这个版本的镜像,会发现可以动态改变环境变量了。
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
,这样构建出来镜像体积会小很多。运用以上一些优化手段,能构建出体积小、构建速度快的镜像。