RUN执行指令

 
Dockerfile.bad
1
2
3
4
5
6
7
FROM ubuntu:20.04
RUN apt-get update
RUN apt-get install -y wget
RUN wget https://github.com/ipinfo/cli/releases/download/ipinfo-2.0.1/ipinfo_2.0.1_linux_amd64.tar.gz
RUN tar zxf ipinfo_2.0.1_linux_amd64.tar.gz
RUN mv ipinfo_2.0.1_linux_amd64 /usr/bin/ipinfo
RUN rm -rf ipinfo_2.0.1_linux_amd64.tar.gz

Dockerfile.good

1
2
3
4
5
6
7
FROM ubuntu:20.04
RUN apt-get update && \
apt-get install -y wget && \
wget https://github.com/ipinfo/cli/releases/download/ipinfo-2.0.1/ipinfo_2.0.1_linux_amd64.tar.gz && \
tar zxf ipinfo_2.0.1_linux_amd64.tar.gz && \
mv ipinfo_2.0.1_linux_amd64 /usr/bin/ipinfo && \
rm -rf ipinfo_2.0.1_linux_amd64.tar.gz

进行比较:

Dockerfile.good 更好的原因是将多个命令合并成一个 RUN\text {RUN} 指令,从而减少了镜像层的数量,提高了构建效率和镜像的可维护性。

文件的复制

`COPY`:复制指令,从上下文目录中复制文件或者目录到容器里指定路径。

ADD:同上,但在执行 <源文件> 为 tar\text {tar} 压缩文件的话,压缩格式为 gzip\text {gzip}, bzip2\text {bzip2} 以及 xz\text {xz} 的情况下,会自动复制并解压到 <目标路径>。

Dockerfile-copy

1
2
FROM python:3.9.5-alpine3.13
COPY test.py /app/test.py

Dockerfile-add

1
2
FROM python:3.9.5-alpine3.13
ADD test.tar.gz /app/
1
docker buildx build -f Dockerfile-copy -t hello-copy .
1
docker buildx build -f Dockerfile-add -t hello-add .

目录操作

Dockerfile-workdir

1
2
3
FROM python:3.9.5-alpine3.13
WORKDIR /app
COPY test.py test.py
1
docker buildx build -f Dockerfile-workdir -t hello-workdir .

ENV 和 ARG

Dockerfile-env

1
2
3
4
5
6
7
8
FROM ubuntu:20.04
ENV VERSION=2.0.1
RUN apt-get update && \
apt-get install -y wget && \
wget https://github.com/ipinfo/cli/releases/download/ipinfo-${VERSION}/ipinfo_${VERSION}_linux_amd64.tar.gz && \
tar zxf ipinfo_${VERSION}_linux_amd64.tar.gz && \
mv ipinfo_${VERSION}_linux_amd64 /usr/bin/ipinfo && \
rm -rf ipinfo_${VERSION}_linux_amd64.tar.gz

Dockerfile-arg

1
2
3
4
5
6
7
8
FROM ubuntu:20.04
ARG VERSION=2.0.1
RUN apt-get update && \
apt-get install -y wget && \
wget https://github.com/ipinfo/cli/releases/download/ipinfo-${VERSION}/ipinfo_${VERSION}_linux_amd64.tar.gz && \
tar zxf ipinfo_${VERSION}_linux_amd64.tar.gz && \
mv ipinfo_${VERSION}_linux_amd64 /usr/bin/ipinfo && \
rm -rf ipinfo_${VERSION}_linux_amd64.tar.gz
1
docker buildx build -f Dockerfile-env -t ipinfo-env .
1
docker buildx build -f Dockerfile-arg -t ipinfo-arg .

环境变量(ENV\text {ENV})和构建参数(ARG\text {ARG})是 Docker\text {Docker} 中用于配置容器和镜像的两种不同机制。主要区别在于它们的作用范围和生命周期:

环境变量(ENV\text {ENV} 构建参数(ARG\text {ARG}
作用范围 环境变量在容器内部运行时可用 构建参数仅在构建镜像的过程中可用
生命周期 环境变量在容器启动后一直存在,只能在容器运行时修改 构建参数只在构建过程中存在,不能在容器内部访问或修改
用途 通常用于配置容器内部的应用程序。 通常用于在构建时动态设置镜像的属性。
用法 ENV MY_VARIABLE=my_value ARG MY_ARG=default_value

构建参数通常用于从构建命令中传递信息到 Dockerfile\text {Dockerfile} 中,例如:

1
2
shellCopy code
docker build --build-arg MY_ARG=new_value -t my_image .

构建参数的值可以在 Dockerfile 中引用,但只在构建过程中有效。

区别:

Docker ARG vs ENV · vsupalov.com



CMD 和 ENTRYPOINT

Dockerfile-cmd

1
2
FROM ubuntu:20.04
CMD ["echo", "hello docker"]
1
docker buildx build -f Dockerfile-cmd -t demo-cmd .

Dockerfile-entrypoint

1
2
FROM ubuntu:20.04
ENTRYPOINT ["echo", "hello docker"]
1
docker buildx build -f Dockerfile-entrypoint -t demo-entrypoint .

Dockerfile-both

1
2
3
FROM ubuntu:20.04
ENTRYPOINT ["echo"]
CMD []
1
docker buildx build -f Dockerfile-both -t demo-both .
  • docker container run --rm -it demo-cmdCMD\text {CMD} 命令和参数。
  • docker container run --rm -it demo-cmd echo "hello world":覆盖 CMD\text {CMD} 命令和参数。
  • docker container run --rm -it demo-entrypointENTRYPOINT\text {ENTRYPOINT} 指定命令和参数。
  • docker container run --rm -it demo-entrypoint echo "hello world":参数送往 ENTRYPOINT\text {ENTRYPOINT} 指令指定的程序作为后续参数。
  • docker container run --rm -it demo-bothENTRYPOINT\text {ENTRYPOINT} 未接收参数且 CMD\text {CMD} 内容为空。
  • docker container run --rm -it demo-both "hello world":参数送往 ENTRYPOINT\text {ENTRYPOINT} 指令指定的程序。

Demo:Python Flask

Dockerfile
1
2
3
4
5
6
7
8
9
10
11
12
FROM python:3.9.5-slim

COPY app.py /src/app.py

RUN pip install flask

WORKDIR /src
ENV FLASK_APP=app.py

EXPOSE 5000

CMD ["flask", "run", "-h", "0.0.0.0"]
1
docker buildx build -f Dockerfile -t demo-flask .
1
docker container run -d -p 5000:5000 demo-flask

云服务器部署端口不能访问

Dockerfile:注意

缓存

在编写 Dockerfile\text {Dockerfile} 时,合理使用缓存是非常重要的,因为它可以显著加速构建过程并减少带宽和资源的使用。Docker\text {Docker} 在构建镜像时会使用缓存来避免重复执行相同的指令。相关指导原则:

  1. 从稳定基础镜像开始:基础镜像通常不会频繁更改,因此选择一个稳定的基础镜像可以使构建过程更快。尽量使用官方或经过验证的基础镜像。
  2. 将不变的层移到前面:在 Dockerfile\text {Dockerfile} 中,将不经常更改的层(例如安装依赖项)放在前面的步骤。这样在更改代码时,只会影响后面的层,前面的层可以重用缓存。
  3. 使用更精细的 COPY:使用 COPY 指令时,尽可能地只复制需要的文件,避免复制整个目录。这可以减少无关文件的复制,从而提高缓存的效果。
  4. 避免不必要的更新:在构建过程中,避免频繁更新软件包或库,除非有明确的需要。频繁的更新可能导致缓存失效,影响构建速度。
  5. 清理不需要的内容:在构建过程中,确保清理不再需要的中间文件或缓存以减小镜像大小。

Dockerfile

1
2
3
4
5
6
7
8
9
10
11
12
FROM python:3.9.5-slim

COPY app.py /src/app.py

RUN pip install flask

WORKDIR /src
ENV FLASK_APP=app.py

EXPOSE 5000

CMD ["flask", "run", "-h", "0.0.0.0"]

修改为:

1
2
3
4
5
6
7
8
9
10
11
12
FROM python:3.9.5-slim

RUN pip install flask

WORKDIR /src
ENV FLASK_APP=app.py

COPY app.py /src/app.py

EXPOSE 5000

CMD ["flask", "run", "-h", "0.0.0.0"]

通常情况下,最佳实践是首先安装应用程序所需的依赖,然后再复制应用程序代码,以便利用 Docker\text {Docker} 的缓存机制来最大程度地减少构建时间。因此,第二个 Dockerfile\text {Dockerfile} 更符合最佳实践,因为它最大程度地减少了构建时的不必要重复操作。

.dockerignore

.dockerignore 文件是一个用于指定在构建 Docker\text {Docker} 镜像时应该排除哪些文件和目录的配置文件。类似于 .gitignore\text {.gitignore} 文件,.dockerignore\text {.dockerignore} 文件允许指定在构建镜像时应该忽略的文件和目录,从而减少构建上下文的大小,提高构建性能,并确保构建出的镜像仅包含必要的内容。

  • 减小构建上下文的大小: 当构建上下文减小时,Docker\text {Docker} 引擎传输的数据量减少,构建时间更短。
  • 减小镜像大小: 构建上下文中不需要的文件和目录不会被包含在镜像中,从而减小镜像的大小。
  • 提高构建性能: 在构建过程中,Docker\text {Docker} 引擎不需要处理 .dockerignore\text {.dockerignore} 中指定的文件,从而提高了构建性能。
  • 避免敏感信息泄漏: 使用 .dockerignore\text {.dockerignore} 可以确保敏感信息或不必要的文件不会被包含在镜像中。

.dockerignore\text {.dockerignore} 文件规则遵循类似于 .gitignore\text {.gitignore} 文件的语法,可以使用通配符和模式匹配来定义要忽略的内容。

  • 通配符:*

    1
    2
    # 排除所有 .log 文件
    *.log
  • 目录通配符:directory/

    1
    2
    # 排除 directory 目录及其内容
    directory/
  • 递归通配符:**

    1
    2
    # 排除 directory 目录下的所有文件和子目录
    directory/**
  • 排除指定文件:!

    1
    2
    # 不排除指定文件
    !important.log
  • 注释:#

  • 规则优先级:按照从上到下的顺序应用。先匹配的规则具有更高的优先级。

分阶段

分阶段构建:在某些情况下,可以使用多阶段构建来减少镜像的大小并优化缓存。

给定 C\text {C} 程序:

1
2
3
4
5
6
#include <stdio.h>

void main(int argc, char *argv[])
{
printf("hello %s\n", argv[argc - 1]);
}

Dockerfile-gcc

1
2
3
4
5
6
7
8
9
10
11
FROM gcc:9.4

COPY hello.c /src/hello.c

WORKDIR /src

RUN gcc --static -o hello hello.c

ENTRYPOINT [ "/src/hello" ]

CMD []

Dockerfile-gcc-phased

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
FROM gcc:9.4 AS builder

COPY hello.c /src/hello.c

WORKDIR /src

RUN gcc --static -o hello hello.c



FROM alpine:3.13.5

COPY --from=builder /src/hello /src/hello

ENTRYPOINT [ "/src/hello" ]

CMD []

这两个 Dockerfile\text {Dockerfile} 都是用于构建一个简单的 C\text {C} 程序,并在容器中运行它。两个 Dockerfile\text {Dockerfile} 都使用了不同的构建策略,一个是单阶段构建,另一个是多阶段构建。这种多阶段构建的方法可以减小最终镜像的大小,因为第一个阶段生成的中间可执行文件在最终阶段中被复制,而不需要包含编译工具和中间文件。这在实际应用中可以节省镜像大小,同时保留了可执行文件以在最终容器中运行。