以下是一些 Dockerfile 技巧,可以让你更快、更安全地构建

2025-06-10

以下是一些 Dockerfile 技巧,可以让你更快、更安全地构建

如今,我们在 Web 开发中大量使用 docker。它易于使用,扩展性极佳,并且为我们提供了一个不可变的环境,用于运行应用程序,从本地开发到部署到生产环境。
为了获得最佳的 docker 体验,您应该实践一些实践,以快速、轻量地构建 docker 镜像。

在本文中,我想根据这个例子向你展示其中的一些做法:

FROM php:7-fpm
WORKDIR /app

COPY . .

ADD https://deb.nodesource.com/setup_12.x .
RUN bash setup_12.x

RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin/ --filename=composer

RUN apt update && \
    apt install -y \
    curl \
    git \
    htop \
    libicu-dev \
    libgd-dev \
    mariadb-client \
    libonig-dev \
    vim \
    unzip \
    nodejs

RUN apt purge -y --auto-remove
RUN npm install -g yarn

RUN docker-php-ext-install \
    exif \
    gd \
    intl \
    mbstring \
    mysqli \
    opcache \
    pdo_mysql \
    sockets

ENV COMPOSER_ALLOW_SUPERUSER 1
RUN composer install

RUN yarn install

RUN yarn run build
Enter fullscreen mode Exit fullscreen mode

根据特定的镜像版本构建

首先要修改的是基础镜像标签。正如您在 Dockerfile 中看到的,虽然使用了 PHP7,但标签名称不够精确。这是我们可以做的第一项改进。

当你使用类似 yarn / composer 的依赖管理器时,你可能会使用锁定文件。使用它们可以在每次安装时保持依赖项的版本完全相同。那么为什么不对所有依赖项都这样做呢?

因此第一个依赖项是我们基于其创建图像的图像标签。

FROM php:7-fpm
...
Enter fullscreen mode Exit fullscreen mode

我们可以将其改为:

FROM php:7.4.25-fpm
...
Enter fullscreen mode Exit fullscreen mode

这样可以避免由于新 PHP 版本的差异导致图像在几个月后无法工作的情况。

最后复制你的代码

Docker 镜像由多个层构建而成。每个层都可以缓存,如果没有任何更改,则此缓存可以在下次构建时重用。只有当所有先前的层也都从缓存中加载时,Docker 才能使用缓存。

...
COPY . /app/
...
Enter fullscreen mode Exit fullscreen mode

您应该根据变更频率来排列构建步骤。您的应用程序代码可能是变更最频繁的部分,因此您应该将其放在尽可能靠后的位置。

FROM php:7.4.25-fpm
WORKDIR /app
## remove COPY from here
...
## rest of commands
...
COPY . .
## final commands
Enter fullscreen mode Exit fullscreen mode

不要使用 ADD 来处理远程依赖关系

ADDDockerfile 中的指令允许你通过 URL 从远程位置复制文件。此功能还可以解压 zip 压缩包,这很棒,但它有一个问题:它不会缓存你的文件。

ADD https://deb.nodesource.com/setup_12.x ./node_setup.bash
RUN bash node_setup.bash && \
    rm node_setup.bash
Enter fullscreen mode Exit fullscreen mode

好的,这样就好多了。

安装脚本文件是不需要的,所以可以在安装后删除。但问题是 Dockerfile 中的层级结构类似于 git 中的提交。当你使用提交将某些内容添加到仓库时,你可以在下次提交时删除它,但由于 git 是增量工作的,所以两个版本都会保留在历史记录中,仓库大小也会增加。
为了避免在 Docker 镜像中出现这种情况,你应该在同一条指令中创建和删除不需要的文件。

RUN curl -sS https://deb.nodesource.com/setup_12.x ./node_setup.bash && \
    bash node_setup.bash && \
    rm node_setup.bash
Enter fullscreen mode Exit fullscreen mode

更好,但仍然不是最好的。

RUN curl -sS https://deb.nodesource.com/setup_12.x ./node_setup.bash | bash -
Enter fullscreen mode Exit fullscreen mode

你可以使用管道在一行命令中完成所有这些操作。在本例中,文件内容将被获取并直接推送到执行它的 bash。

在 Dockerfile 中使用 Composer

现在,我们已经在容器中安装了 Composer。它将适用于所有环境。将其保留在最终镜像中并非最佳选择,因为这不是必需的,而且可能会增加一些漏洞。在多阶段构建中使用 Composer 还有一个更好的选择,我将在下一篇文章中讨论。

...
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin/ --filename=composer
...
Enter fullscreen mode Exit fullscreen mode

这行代码没问题,它会被缓存,不会留下任何垃圾信息。
也许我们应该使用官方安装脚本中提供的哈希校验脚本。
你也可以使用这个技巧:

...
COPY --from=composer:2.1.11 /usr/bin/composer /usr/bin/composer
...
Enter fullscreen mode Exit fullscreen mode

这将从外部官方作曲家图像中复制作曲家箱。

安装 apt 软件包

接下来,我们使用 apt 管理器安装了一些软件包。让我们检查一下它们是否都需要。git

可能需要用来从源代码拉取软件包或构建一些二进制文件。我看不出有什么理由保留它。我们暂时把它移除吧。htop

可能对调试有用,但对最终镜像来说没用,我们可以在真正需要的时候再安装它。Vim 也没什么用,因为你不应该在工作容器中进行任何更改。它是无状态的,所以你的更改会在重启后消失。此外,mariadb-client 可能只在开发时才需要。

其余软件包可能是必需的,但还有一个问题。docker 使用层进行缓存。每一层都是通过 dingle 指令构建的。如果该指令或前一条指令发生更改,缓存将失效。因此,在这种情况下,如果您不更改此指令,则可能永远不会安装较新的软件包,并且它们可能会因构建环境而异。

如果为每个软件包添加特定版本,则可以确保从此 Dockerfile 构建的每个镜像都具有相同版本的软件包,并且缓存将正确失效。

您可以通过在=符号后指定版本来做到这一点。要检查需要安装哪个版本,请转到当前工作容器或构建镜像的容器,然后使用 list 命令进行检查:

$ apt list libonig-dev
Listing... Done
libonig-dev/stable,now 6.9.6-1.1 amd64 [installed]
Enter fullscreen mode Exit fullscreen mode

在这个例子中,当前工作的版本是5.5.9999+default,所以让我们检查其余的并指定它们。

RUN apt update && \
    apt install -y \
    libicu-dev=67.1-7 \
    libgd-dev=2.3.0-2 \
    libonig-dev=6.9.6-1.1 \
    unzip=6.0-26 \
    nodejs=12.22.7-deb-1nodesource1

RUN apt purge -y --auto-remove
Enter fullscreen mode Exit fullscreen mode

当然,你需要手动保持更新。经常检查是很好的。

还有一件事要做。在安装命令之后,有一个命令会在安装指令后清理你的系统。这个命令在这里很好,但它是在单独的指令中完成的。我们记得,如果我们删除了其他层上的某些内容,这些内容仍然会存在于最终镜像的先前层中。所以,让我们用同一个命令来执行清理操作。这应该会减小最终镜像的大小。

RUN apt update && \
    apt install -y \
    libicu-dev=67.1-7 \
    libgd-dev=2.3.0-2 \
    libonig-dev=6.9.6-1.1 \
    unzip=6.0-26 \
    nodejs=12.22.7-deb-1nodesource1 && \
    apt purge -y --auto-remove
Enter fullscreen mode Exit fullscreen mode

Composer 依赖项

让我们开始下一行。还有一条RUN指令,它将安装我们所有的 Composer 依赖项。这里首先忽略了一点,那就是我们安装的所有依赖项都与开发依赖项一起安装,而这些依赖项对于运行环境来说并非必需。所以,我们在这里添加一些标志。

RUN composer install --optimize-autoloader --no-dev
Enter fullscreen mode Exit fullscreen mode

这些标志将安装所有依赖项(不包括 dev),并进行自动加载优化。

正如你所记得的,我们必须COPY尽可能地将代码指令从文件的开头移到结尾。这里是我们需要项目文件的地方。但是我们需要整个代码库吗?你多久修改一次项目中的依赖项?肯定比应用程序代码少。那么,每次修改代码时,我们都需要拉取依赖项吗?可能不需要😃

所以我们唯一需要的文件就是那里的 Composer 文件。

COPY composer.json .
COPY composer.lock .
RUN composer install --no-dev --no-scripts
Enter fullscreen mode Exit fullscreen mode

现在缓存将适用于我们的作曲家依赖项。

代码

好的,现在我们需要代码了,因为这里有构建步骤。让我们COPY从头到尾把说明粘贴到这里。

COPY . .
Enter fullscreen mode Exit fullscreen mode

现在,我们需要使用所有项目文件生成自动加载文件

RUN composer dumpautoload --optimize
Enter fullscreen mode Exit fullscreen mode

节点依赖关系

对于 Node,情况与 Composer 相同。因此,首先复制软件包文件,然后安装所有依赖项。

RUN yarn install

RUN yarn run build
Enter fullscreen mode Exit fullscreen mode

我们需要所有依赖项,还是只需要非开发依赖项?也许我们不需要容器中的任何节点依赖项,因为我们只用它来构建前端。那么,为什么不先安装所有依赖项,然后在构建完成后再移除呢?

RUN yarn install && \
    yarn run build && \
    rm -rf node_modules && \
    yarn cache clean
Enter fullscreen mode Exit fullscreen mode

目前,我们没有任何不必要的节点依赖项。问题在于我们无法缓存这些依赖项。有两种方法可以解决这个问题。第一种方法是多阶段构建,但这是另一篇文章的主题,该文章即将发布。第二种选择是将整个前端构建过程移至 nginx Dockerfile。

目前的价值

应用所有这些更改后,让我们检查一下构建过程节省了多少时间。

旧镜像构建 4 分 28 秒* 901MB

新镜像构建 3 分 57 秒* 711MB

最终镜像节省了近 200MB 空间。我们的构建时间并没有比以前好多少,但让我们检查一下缓存现在的运行情况:

带缓存的旧镜像 4 分 35 秒*

带缓存的新镜像 25.1 秒*

没错,新镜像的缓存效果更好了。

您真的需要节点来运行 PHP 应用程序吗?

在我们的示例 Dockerfile 中,我们在后端容器中构建前端应用程序,然后将其复制到前端容器中:

FROM nginx:latest

COPY --from=backend /app/public /app/public

COPY docker/nginx/default.conf /etc/nginx/default.conf
Enter fullscreen mode Exit fullscreen mode

为什么不直接在前端图像中构建我们的应用程序。

FROM nginx:1.21.4
WORKDIR /app

COPY docker/nginx/default.conf /etc/nginx/default.conf

RUN curl -sS https://deb.nodesource.com/setup_12.x ./node_setup.bash | bash -

RUN apt install nodejs=12.22.7-deb-1nodesource1 && \
    apt purge -y --auto-remove

COPY . .

RUN npm install -g yarn

RUN yarn install && \
    yarn run build && \
    rm -rf node_modules && \
    yarn cache clean
Enter fullscreen mode Exit fullscreen mode

我们的后端 Dockerfile

FROM php:7.4.25-fpm
WORKDIR /app

COPY --from=composer:2.1.11 /usr/bin/composer /usr/bin/composer

RUN apt update && \
    apt install -y \
    libicu-dev=67.1-7 \
    libgd-dev=2.3.0-2 \
    libonig-dev=6.9.6-1.1 \
    unzip=6.0-26 && \
    apt purge -y --auto-remove

RUN docker-php-ext-install \
    exif \
    gd \
    intl \
    mbstring \
    mysqli \
    opcache \
    pdo_mysql \
    sockets

ENV COMPOSER_ALLOW_SUPERUSER 1

COPY composer.json .
COPY composer.lock .
RUN composer install --no-dev --no-scripts

COPY . .
RUN composer dumpautoload --optimize
Enter fullscreen mode Exit fullscreen mode

因此,目前我们的后端镜像(无缓存)构建耗时 3 分 8 秒*,有缓存构建耗时 6 秒*,大小为 597MB。

前端镜像构建耗时 57 秒*,大小为 310MB。

您可以并行构建它们,因此最终时间可能是其中一个镜像的最大构建时间。

多阶段构建

所有这些变化或许在使用名为“多阶段构建”的功能后会更加完善。
这个主题应该很快就会在我的下一篇博客中发布😃

编辑:现在可用


*本文中出现的所有时间,我都是在配备英特尔 i5 和 16GB RAM 的 Mac 上进行的。

请记住在您的 docker 镜像中使用非 root 用户。

最初发布于mateuszcholewka.com

继续阅读 https://dev.to/mtk3d/here-are-the-dockerfile-tips-you-can-use-to-get-your-builds-faster-and-safer-4o1a
PREV
关于 Firebase,你需要知道的一切 Firebase 是什么?使用 Firebase 的优势(主要优势)Firebase API Firebase 服务 Firebase 是否免费使用?结论
NEXT
身份验证漏洞