面向前端开发人员的 Docker:用于开发的自定义 Docker 镜像

2025-06-10

面向前端开发人员的 Docker:用于开发的自定义 Docker 镜像

作者:本杰明·马丁

让我们花点时间思考一下,对于本地开发来说,什么才是最重要的。对我来说,我希望确保所有开发人员都使用相同的依赖项,并且不必担心他们安装了什么版本。不再需要“但它在我的机器上可以运行”这样的借口。同时,我希望确保我们保留 HMR(热模块替换)的便利性,这样开发人员就无需不断刷新应用程序就能看到他们的更改。我们不想失去快速的反馈。

在本文中,我们将研究如何为具有自定义的样板 VueJS 应用程序设置 Docker, Dockerfile从中构建我们的图像和容器,以及如何从中获得效率。

如果您错过了本系列的第一部分, 请点击此处了解更多关于 Docker 命令行界面的信息。本节需要使用第一部分中的命令。如果您已经熟悉 Docker CLI,请继续阅读。

先决条件:创建我们的项目

这当然是一篇关于 Docker 的文章,所以请确保你已经安装了 Docker。你可以按照 Docker 的官方安装说明进行操作。由于我使用的是 Vue,所以我使用 VueCLI 快速启动了一个工作区 vue create docker-demo

我选择的配置(如下所示)将与 E2E 测试和单元测试相关,它们将成为我们 CI/CD 管道的一部分。

Vue CLI 创建项目

一旦所有东西都安装完毕, cd 进入我们的新项目文件夹,打开一个 IDE,让我们开始吧。

用于开发的自定义 Docker 镜像

如果您使用过 Docker 但从未构建过自己的镜像,您可能知道我们在执行 docker run 命令时会指定一个镜像。这些镜像是从 Docker Hub 或其他远程仓库(如果本地找不到该镜像)拉取的。但在本例中,我们想要构建一个自定义镜像。

在项目根目录中,创建一个名为 的文件 Dockerfile.dev。这将是我们的开发镜像。在该文件中,将以下代码复制到其中。

# Base Image
FROM node:9.11.1

ENV NODE_ENV=development
ENV PORT=8080

WORKDIR /usr/src/app
COPY package*.json /usr/src/app/
RUN cd /usr/src/app && CI=true npm install

EXPOSE 8080
CMD ["npm", "run", "serve"]

好吧……但这都有什么用呢?让我们深入研究一下。

Dockerfile 命令和关键字

FROM 指定构建自定义镜像时所基于的现有镜像。由于我们运行的是 Node.js 应用程序,因此我们选择了其官方 Docker 镜像之一。

FROM node:9.11.1 表示我们的应用程序映像将从节点 v 9.11.1 映像开始

ENV 设置环境变量

ENV PORT=8080 设置环境变量 PORT 以供稍后使用

ENV NODE_ENV=developmentNODE_ENV 设置应用程序中使用的 环境变量 

WORKDIR 设置容器内的工作目录

WORKDIR /usr/src/app 定义 /usr/src/app/ 为 Docker 镜像中的工作目录

COPY 将新文件、目录或远程文件复制到容器/镜像中

COPY package*.json /usr/src/app/ 将我们的 package.json 和 package-lock.json 复制到我们的工作目录中

RUN 在当前镜像之上的新层中执行命令并提交。运行构建时,您将看到一个哈希值,代表最终镜像的每一层

RUN cd /usr/src/app/ && CI=true npm install 将工作目录更改为 package.json 所在的位置,并将所有依赖项安装到镜像中的此文件夹中。这使得镜像保存了依赖项的冻结副本。我们的 Docker 镜像(而不是主机)负责管理我们的依赖项

EXPOSE 允许我们从主机访问容器上的端口

EXPOSE 8080 与容器内运行应用程序的端口匹配,并允许我们从主机访问我们的应用程序

CMD 提供容器创建时运行的默认初始化命令,类似启动脚本

CMD ["npm", "run", "serve"] 将其设置为启动容器时的默认命令。该命令在构建镜像时不会运行,它仅定义容器启动时应该运行的命令。

我知道你急着想让这一切顺利进行,但请稍安勿躁。让我们  仔细 看看Dockerfile.dev 我们 为什么 要这么做。

Dockerfile 结构建议

那么, 我的应用程序在哪里?

对。我们没有使用 命令复制整个工作区。如果这样做,每次代码修改 后 COPY 都需要运行  。我们不想在开发过程中一遍又一遍地重复这个过程。这样可以提高效率。docker builddocker run

缓存依赖项

我们利用了 Docker 的镜像分层机制。Docker 构建镜像时,您会看到每个完成的层的哈希值。更重要的是,Docker 还会缓存这些层。如果 Docker 发现该层与上次构建相比没有任何变化(并且之前的层也完全相同),那么 Docker 就会使用该层的缓存版本,从而节省您和您的开发人员的宝贵时间!当某个层发生变化时,其上的所有缓存层都将失效并被重新构建。

package.json 因此,如果我们的或 没有改变 ,package-lock.json 那么我们的整个图像是可缓存的,不需要重建!

优先事项

这也是为什么我们需要将其他 Dockerfile 不太频繁更改的命令放在文件顶部的原因。一旦缓存的某一层失效(例如,切换 ENV PORT=8080 到另一个端口),该缓存层及其之后的所有缓存层都会失效,Docker 将不得不重建这些缓存层。

构建自定义 Docker 镜像

现在,使用以下命令构建图像: docker build --tag docker_demo:latest --file Dockerfile.dev .

从自定义 Dockerfile 构建 Docker

--tag 在命令中 使用 可以让我们轻松地从 命令docker build 中引用该图像 docker run

命令末尾 . 的 docker build 引用了我们的自定义项所在的上下文 Dockerfile 。因此,此命令应从项目目录的根目录运行。

您可以使用来运行它 docker run docker_demo:latest,但不幸的是,我们还需要做更多的工作才能让它从命令行快速轻松地运行。

运行我们的容器:生活质量的提升

我们将 docker run 每天执行该命令,甚至更频繁。但是,如果我们直接执行该 docker run docker_demo:latest 命令,Docker 每次都会创建一个  容器。除非你明确地停止旧容器,否则 Docker 不会停止旧容器。这在很多情况下非常有用,但由于我们对主机端口进行了硬编码,因此在主机上可能会遇到端口冲突。

为了方便我们停止和移除旧容器,我们应该给它们命名,以便以后轻松引用。此外,我希望在取消正在运行的进程时,正在运行的容器也能被移除。

docker run --rm -it\
--name docker_demo_container\
docker_demo:latest

运行我们构建的 Docker 镜像

添加了什么?

我们 --name 在运行命令的末尾添加了一个字段。这样我们就可以引用容器,而无需查找哈希值。现在,我们可以轻松地通过名称来停止容器。

我们还在 命令 中 添加了--rm 和 标志 。该  标志指示 Docker 在容器停止时移除容器。该  标志指示容器启动后,终端仍保持活动状态并可交互。-itdocker run--rm-it

挂载主机目录

让我们回到 docker run 命令,找到将工作区目录挂载到容器内文件夹的方法。我们可以通过在命令中添加容器的挂载点来实现 docker run 。这将告诉 Docker,我们想要在主机文件夹 ( src) 和 Docker 容器文件夹 ( dst) 之间创建一个活动链接。我们的新命令应该如下所示:

docker run --rm -it\
--name docker_demo_container\
--mount type=bind,src=`pwd`,dst=/usr/src/app\
docker_demo:latest

但这可能会与主机 node_modules 文件夹冲突,因为我们要将整个文件挂载 pwd 到镜像中应用程序的位置(以防我们的某个开发人员意外地 npm install 在其主机上运行)。因此,让我们添加一个卷,以确保我们保留 node_modules 容器中存在的文件。

docker run --rm -it\
--name docker_demo_container\
--mount type=bind,src=`pwd`,dst=/usr/src/app\
--volume /usr/src/app/node_modules\
docker_demo:latest

访问容器内的端口

如果您尝试了上述命令(并且正在运行 VueJS 应用程序),您应该会看到:

 App running at:
  - Local:   http://localhost:8080/

  It seems you are running Vue CLI inside a container.
  Access the dev server via http://localhost:<your container's external mapped port>/

Docker 提示我们需要从容器中暴露一个端口,并将其发布到主机上。我们可以 --publish 在 run 命令中添加标志来实现这一点。(我们 EXPOSE 的 中已经有这个命令 了Dockerfile.dev

--publish <host-port>:<container-port> 告诉 Docker,到主机端口(即通过 localhost)的流量 应该被导向 您定义的<host-port> 容器 。<container-port>

docker run 在一个命令中

让我们看一下最终的运行命令:

docker run --rm -it\
--name docker_demo_container\
--publish 4200:8080\
--mount type=bind,src=`pwd`,dst=/usr/src/app\
--volume /usr/src/app/node_modules\
docker_demo:latest

成功运行我们构建的 Docker 镜像

运行上述命令最终将允许我们通过 http://localhost:4200访问我们的应用程序。

测试一下

让我们创建一个新的副本并运行它。如果您尝试更改我们文件的某个模板,您会发现一切仍然正常运行。

但是说到测试,单元测试怎么样?好吧,一旦我们的容器运行起来,我们就可以打开一个新的终端和 docker exec 一个在容器中运行的命令。

docker exec -it docker_demo_container npm run test:unit

通过 Docker 运行单元测试

上述命令将与我们的容器创建一个交互式终端连接 docker_demo_container 并在其中执行命令 npm run test:unit ,从而允许我们为我们的应用程序运行单元测试。

结束语

现在,我们可以构建开发镜像并在本地运行,同时保留热模块替换的便利性,从而保持高效的开发工作流程。开发人员无需担心主机上的依赖项与镜像中的依赖项发生冲突。再也不用担心“但它在我的机器上可以运行”这样的借口了。此外,我们还拥有一个可以轻松运行的命令来执行单元测试。

如果您发现我遗漏了任何内容或想进一步了解 Docker,请联系我!

鏂囩珷鏉ユ簮锛�https://dev.to/rangle/docker-for-frontend-devs-custom-docker-images-for-development-1afc
PREV
将 MongoDB(Mongoose)添加到 Next.js API
NEXT
JavaScript Date 对象终于被替换了😱