面向 JavaScript 开发人员的 Docker 简介(功能包括 Node.js 和 PostgreSQL)
本教程的所有代码(完整包)均可在此仓库中找到。如果您觉得本教程有用,请与您的朋友和同事分享!
想要了解更多类似教程,请在 Twitter 上关注我@eagleson_alex
另有视频版本可供下载:
目录
- 介绍
- 什么是 Docker?
- 先决条件
- 安装 Docker
- 创建容器
- 创建 Node 应用程序
- 弃用 Node App
- 创建 Dockerfile
- Docker 层和缓存
- 添加 Docker 卷
- 什么是 Docker-Compose?
- 添加数据库
- 将应用程序连接到数据库
- 添加前端
- 创建 Docker Compose YML 文件
- 添加 pgAdmin 面板(奖励)
- 有用的 Docker 命令
- 总结
介绍
在本教程中,您将通过构建一个带有前端和 PostgreSQL 数据库的全栈 Node.js 应用程序来了解 Docker 是什么以及它的用途。
我们将使用 Docker Compose 将每个容器连接并联网在一起,以便它们易于在项目贡献者之间共享,并部署到您选择的任何托管服务。
什么是 Docker?
Docker是一个工具,它允许你将应用程序的运行环境与应用程序本身一起打包。你只需Dockerfile
在项目中添加一个名为 .docker.com 的文件即可轻松实现这一点。
它使用一种称为容器的概念,这些容器比完整的虚拟机更轻量级(所需资源更少),用于为您的应用程序创建环境。这些容器被设计为极易移植,这意味着您可以快速将它们部署到任何地方,并且只需部署更多容器副本即可快速扩展您的应用程序。
您只需在容器中定义环境要求Dockerfile
(例如 Ubuntu 18、Node.js 等),每次在任何机器上启动容器时,它都会重新创建该环境。这样,您就能提前知道不会遇到依赖项缺失或版本错误的问题。
话虽如此,但向那些刚进入开发领域、尚未经历过 Docker 所解决的很多问题的人真正展示 Docker 的必要性可能是一项挑战。
本教程旨在模拟您在工作环境中可能遇到的几个现实场景,并展示 Docker 如何帮助解决这些问题。
场景
在这个例子中,我们将复制两个常见的开发问题:
-
贵公司的项目依赖于开发团队在其机器上安装的旧版本的工具(在我们的例子中是Node.js )
-
我们希望能够轻松地使用开发人员本地机器上的数据库副本来测试应用程序,而无需他们安装数据库软件(在我们的例子中是 PostgreSQL)
如果你按照本教程操作,你将可以在你的机器上运行一个可以运行的应用程序,并且可以查询 Postgres 数据库,而无需安装 Node.js 或 Postgres。你唯一需要的工具就是 Docker。
可扩展性
先决条件
本教程唯一需要安装的先决软件是 IDE(代码编辑器,我使用 VS Code)和 Docker。
Docker 的安装方式取决于你正在运行的操作系统。我在 Windows 11 上的WSL2上运行 Docker ,体验非常棒。它在 Mac 和 Linux 上也同样有效,你只需按照操作系统的安装说明进行操作即可。
我推荐使用 Docker Desktop,它能提供良好的图形用户界面 (GUI) 来操作 Docker,但这不是必需的。本教程将完全通过命令行来管理 Docker(不过我可能会使用 Docker Desktop 的屏幕截图来展示正在发生的事情)。
我还建议你也安装Node.js。技术上来说,没有它也可以,但在开始的几个步骤中,我们将在 Docker 介入之前在本地运行该应用。这也有助于演示 Docker 如何修复我们的版本问题。
安装 Docker
安装 Docker 后,让我们确保它能正常工作。输入以下命令:
docker --version
您应该会得到一个版本号(而不是“未找到”)。我现在显示的版本是 20.10.11,但任何接近该数字的版本都应该可以正常工作。
大多数容器都托管在名为Docker Hub的服务上,包括我们将要使用的容器。
让我们从测试最简单的容器开始hello-world
。
创建容器
运行以下命令下载hello-world
镜像:
docker pull hello-world
这将从 Docker Hub 拉取镜像。需要注意的是,我们还没有创建容器。Docker 镜像是一组关于如何创建容器的指令。如果您熟悉 Web 开发,可以将镜像想象成 HTML(蓝图),将容器想象成 DOM(结构)。
您可以在默认图像说明中添加其他说明,Dockerfile
我们很快就会讲到。
假设您收到了类似的成功消息Status: Image is up to date for hello-world:latest
,那么您就可以创建容器了。
docker run hello-world
如果成功,您将在终端中看到以下输出:
Hello from Docker!
This message shows that your installation appears to be working correctly.
To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
(amd64)
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.
To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bash
Share images, automate workflows, and more with a free Docker ID:
https://hub.docker.com/
For more examples and ideas, visit:
https://docs.docker.com/get-started/
恭喜!您已经运行了第一个 Docker 容器!虽然使用 Docker Desktop 可以非常轻松地管理它,但让我们看几个在命令行上管理它的最常用命令:
docker image ls
# OR
docker container ls
将显示您系统中当前所有镜像或容器的列表。由于hello-world
它在打印完测试消息后会立即停止,因此它不会像运行 Web 应用的容器那样一直运行。您不会在容器列表中看到它,但会在镜像列表中看到它。
图像/容器的 ID 和名称对于查找都很重要,因为它们允许您引用这些图像/容器来启动/停止它们。
当你停止运行容器时,它不会被删除。这真是太好了!这意味着下次需要时,只需快速启动它,无需再次下载和安装。
在使用 Docker 时,你会发现,当你修改内容或构建新版本时,这些镜像和容器有时会开始堆积。要快速删除所有旧的/未使用的镜像和容器,你可以运行:
docker image prune
# OR
docker container prune
如果这些现在看起来没有什么帮助,请不要担心,但要记住它们,因为您以后可能会想要参考它们。
创建 Node 应用程序
在深入研究 Docker 之前,我们先来构建一个小型的 Web 应用,用来演示 Docker 的一些高级功能。我们将使用 Node.js 和 Express 构建一个简单的 Web 服务器:
我创建了一个名为 的新空目录docker-template
,并在其中初始化了一个 NPM 仓库。
mkdir docker-template
cd docker-template
npm init
npm install express
server.js
const express = require("express");
const app = express();
const port = 8080;
app.get("/", async (req, res) => {
res.setHeader("Content-Type", "text/html");
res.status(200);
res.send("<h1>Hello world</h1>");
});
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`);
});
现在运行你的应用程序:
node server.js
然后访问http://localhost:8080查看:
我们希望为这个项目实现的另一个功能是文件监视以及文件更改时服务器的自动重新加载。
最简单的方法是使用名为nodemon的工具。
npm install nodemon --save-dev
然后将start
脚本添加到您的package.json
文件:
package.json
{
"name": "server",
"version": "1.0.0",
"description": "",
"main": "server.js",
"scripts": {
"start": "nodemon server.js"
},
"author": "me",
"license": "ISC",
"dependencies": {
"express": "^4.17.2",
},
"devDependencies": {
"nodemon": "^2.0.15"
}
}
使用以下命令运行你的应用:
npm run start
尝试在您的应用程序运行时编辑您的server.js
文件(将“hello world”更改为“hello world!!!!”或其他内容)并验证您的 Node 应用程序是否重新加载,并且当您点击刷新按钮时您是否在浏览器中看到更改(文件监视不会自动触发浏览器刷新)。
一旦有效,继续下一步!
弃用 Node App
接下来的部分有点意思。我们故意把这个服务器变成一个遗留项目。
我们假设你正在运行 Node.js 15 或更高版本。你可以运行以下命令进行检查:
node --version
我的输出是v16.11.1
。如果你的 Node 版本超过 15,你可以使用NVM或者直接继续阅读。本部分不需要在你的机器上安装特定的 Node 版本。事实上,这正是我们下一节要用 Docker 解决的问题。
在 Node 15 中,未处理的拒绝 Promise 的处理方式发生了重大变化。在 Node 15 之前版本中,如果 JavaScript Promise 被拒绝且未使用 catch 语句,则会发出警告并继续运行;但在 Node 15 之后,未处理的 Promise 会导致程序崩溃。
因此,我们可以添加一些代码,使我们的服务器能够在 Node 15 之前的版本上运行,但不能在 Node 的新版本上运行。
现在就开始吧:
server.js
// @ts-check
const express = require("express");
const app = express();
const port = 8080;
app.get("/", async (req, res) => {
res.setHeader("Content-Type", "text/html");
res.status(200);
res.send("<h1>Hello world</h1>");
});
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`);
});
const myPromise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("good");
}, 300);
reject("bad");
});
myPromise.then(() => {
console.log("this will never run");
});
上面的代码创建了一个始终会拒绝的新 Promise。它可以在 Node.js v14 上运行(并显示警告),但在 v15 及更高版本上会崩溃。尝试在 v15 及更高版本上运行它,你会得到code: 'ERR_UNHANDLED_REJECTION'
。
现在显然我们可以...添加一个 catch 块(或完全删除代码),但我们正在尝试复制一个您使用旧代码库的情况,并且您可能不一定有这些可用的选项。
假设出于某种原因,这个应用必须在 Node v14 或更早版本上运行才能正常工作。团队中的每个开发人员都必须准备好在该环境中操作……但我们公司还有一个在 Node v17 上运行的新应用!所以我们也需要那个环境。
话说回来,别的工具居然还在用 X 版本!我的机器上只有 Y 版本!谁知道我团队其他成员用的是什么版本,或者我把应用发给测试人员用的版本。
我该怎么办!?
输入 Docker。
创建 Dockerfile
使用 Docker,我们可以使用代码来生成应用程序的运行环境。首先,我们在 Docker Hub 中搜索 Node.js 镜像。官方的 Node 镜像名为node。
你会注意到,当你查看支持的标签时,会发现有很多版本。就像你的机器上只有一个版本一样,几乎所有你想要的版本都有对应的 Docker 镜像。当然,Node 本身需要安装在某种操作系统上,所以这通常是标签的另一部分。
默认的 Node 映像在Debian上运行,但是最流行的版本之一在称为Alpine Linux 的系统中运行。
Alpine 受欢迎的主要原因是其体积小巧,它是一个 Linux 发行版,旨在精简所有必要的组件。这意味着在这个镜像上运行和分发我们的应用会更快、更经济(假设它满足我们的需求)。
对于我们的简单应用程序来说,确实如此。
记住,我们特别想要一个旧版本的 Node(比 v15 更旧,这样我们的应用程序才能正常运行而不会崩溃),所以我将选择带有 标签的图像node:14-alpine3.12
。也就是 Node v14 和 Alpine v3.12。
docker pull node:14-alpine3.12
我们可以像之前一样使用 提前拉取镜像hello-world
,但这不是必须的。通过将其添加到我们的Dockerfile
Docker 中,如果在我们的机器上找不到它,Docker 会自动从 Docker Hub 中拉取它。
让我们Dockerfile
在项目根目录中创建一个名为 (无扩展名)的文件server.js
:
Dockerfile
# select your base image to start with
FROM node:14-alpine3.12
# Create app directory
# this is the location where you will be inside the container
WORKDIR /usr/src/app
# Install app dependencies
# A wildcard is used to ensure both package.json AND package-lock.json are copied
# where available (npm@5+)
# copying packages first helps take advantage of docker layers
COPY package*.json ./
RUN npm install
# If you are building your code for production
# RUN npm ci --only=production
# Bundle app source
COPY . .
# Make this port accessible from outside the container
# Necessary for your browser to send HTTP requests to your Node app
EXPOSE 8080
# Command to run when the container is ready
# Separate arguments as separate values in the array
CMD [ "npm", "run", "start"]
我添加了很多注释来帮助解释 Dockerfile 的每个部分。您可以在这里了解更多关于 Dockerfile 的信息,我强烈建议您浏览一下该页面,以熟悉可用的命令。
在我们继续之前,我想简单谈谈 Docker 的层和缓存,因为它们是非常重要的主题!
Docker 层和缓存
对于像这样的简单 Dockerfile,一个常见的问题是:
“为什么要使用两次 COPY 命令?由于第二次 COPY 命令会复制整个目录,所以第一次 COPY 不是没有必要的吗?”
答案实际上是“否”,原因在于 Docker 的最佳功能之一“层”。
每次使用 FROM、COPY、RUN 或 CMD 之一时,它都会基于前一层创建另一个镜像。该镜像可以缓存,只有在发生更改时才需要重新创建。
因此,通过在 上创建特定的 COPY 行,package-*.json
我们将在运行 之前创建一个基于该文件内容的缓存层npm install
。这意味着,除非我们更改 package.json
,否则下次构建 Docker 时将使用npm install
已经运行过的缓存层,这样我们就不必在每次运行 时都安装所有依赖项docker build
。这将为我们节省大量时间。
下一个 COPY 操作会检查项目目录下的所有文件,这样当任何文件发生更改时(基本上就是我们更新package.json
应用程序以外的任何内容时),该层都会被重建。但这正是我们想要的。
这只是使用 Docker 时可以利用的效率的一个例子,但我鼓励您阅读Dockerfiles 的完整最佳实践列表。
构建应用容器
现在您的 Dockerfile 已经创建,在构建之前我们只需要做最后一件事。
与您可能熟悉的类似.gitignore
(用于防止将自动生成的文件和私人机密提交到公共存储库),Docker 具有类似的概念,可以防止您不必要地复制容器不需要的文件。
.dockerignore
现在让我们创建一个文件:
.dockerignore
node_modules
npm-debug.log
这两者都将在容器内生成,因此我们不想复制它们的本地版本。
现在我们就可以开始构建了。运行以下命令:
docker build . -t my-node-app
这将在当前目录中构建 Dockerfile 描述的镜像.
,并将其命名为my-node-app
。完成后,您可以通过以下命令查看镜像及其所有详细信息:
docker image ls
创建镜像后,我们现在就可以从镜像中构建一个容器来运行我们的应用程序了:
docker run -p 3000:8080 --name my-node-app-container my-node-app
此命令告诉 Docker 使用我们的镜像构建一个正在运行的容器。该--name
标志允许我们为容器命名(以便于识别以及后续的停止/启动,否则容器名称将随机生成)。
我使用名称来将其与最后一个参数区分开来,后者是我们正在构建的图像my-node-app-container
的名称( )。my-node-app
我们使用该-p
标志将主机(我们的计算机)环境的端口绑定到容器环境。
如果你还记得的话,我们EXPOSE 8080
在 Dockerfile 中写了应用程序运行的端口。上面的命令将我们机器上的端口 3000 映射到容器中的端口 8080。
(请注意,如果您愿意,您可以映射相同的端口,如 8080:8080,我们只是在这个例子中混合了它以表明这是可能的)
使用以下命令再次检查容器是否成功启动:
docker container ls
我的输出如下:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b6523b2602e1 my-node-app "docker-entrypoint.s…" 6 minutes ago Up 6 minutes 0.0.0.0:3000->8080/tcp my-node-app-container
(如果文字换行,导致排列不整齐,请见谅)
我们可以看到容器已经启动了 X 分钟。这意味着我们的应用正在 8080 端口上运行,我们可以通过 3000 端口访问我们机器上的 8080 端口,因此打开浏览器访问http://localhost:3000/即可查看:
太棒了!您已经创建了第一个自定义 Docker 镜像和容器,并在其中运行您自己的应用程序!
现在你已经设置好了环境,接下来自然而然地,你可能想做的事情之一就是更新你的应用。如果你修改了server.js
文件并保存了,那么在重新加载页面时,你还能看到这些变化吗?
不会的。该应用程序基于server.js
容器内部的副本运行,该副本与项目目录中的副本没有直接关系。有什么方法可以将它们“连接”起来吗?
当然有,我们需要引入Docker卷。
添加 Docker 卷
Docker 使用卷的概念允许您在正在运行的容器之间保存数据。
您可以想象您可能希望让您的应用程序保存一些数据,但是根据 Docker 的工作方式,您的容器被设计为可以随意销毁和重新创建。
使用卷主要有两种方式。您可以预先创建一个卷并为其命名。这将默认将所有卷数据保存在/var/lib/docker/volumes
目录中(在 Linux 环境中,该目录在 Windows 上可能有所不同,但相同)。
要创建命名卷(您不需要为本教程运行此命令,它只是一个示例):
docker volume create my-named-volume
然后,您可以将容器中的任意目录映射到计算机上的相应目录。您可以通过--volume
在docker run
命令中添加标志来实现,如下所示--volume my-named-volume:/usr/src/app my-node-app
:
该示例会将容器中的工作目录映射到计算机上的 Docker 卷。但这对我们没有任何帮助,因为我们希望将特定目录(我们的项目目录)与容器中的目录同步,以便我们可以编辑项目中的文件,并使其在容器中更新。
我们也可以这样做。
首先,我们需要停止现有的容器(没有卷),将其删除,然后使用该卷再次运行它:
docker container stop my-node-app-container
docker container rm my-node-app-container
docker run -p 3000:8080 --name my-node-app-container --volume ${PWD}:/usr/src/app my-node-app
在大多数终端中,PWD 的意思是“打印工作目录”,因此它会将当前目录映射到/usr/src/app
容器内的目录。这将实现我们在计算机上的项目和容器中的项目之间同步文件的目标。
由于我们已经在本教程前面设置了文件监视和重新加载nodemon
,因此您现在应该能够server.js
在容器运行时在项目目录中进行编辑(只需编辑 hello world 文本),然后刷新浏览器以查看更改。
就这样!现在你有一个 Dockerized Node 应用,你可以在你的机器上进行更改,并在容器内实时查看更新。
至此,我们对 Docker 本身的介绍基本完成了。我们完成了第一个“场景”的实现,在这个场景中,我们使用代码指令重新创建了应用程序运行所需的环境。
现在我们需要解决第二个常见场景:为了正常运行,我们的应用程序依赖于其他服务,例如数据库。从技术上讲,我们可以在 Dockerfile 中添加安装数据库的指令,但这实际上无法模拟我们应用程序的部署环境。
我们的 Node 应用和数据库不一定托管在同一台服务器上。事实上,这几乎是不可能的。不仅如此,我们也不想为了编辑数据库而启动 Web 服务器,反之亦然。有没有办法让我们仍然可以使用 Docker,但又能隔离相互依赖的多个服务呢?
是的,我们可以。
什么是 Docker-Compose?
用他们自己的话来描述最好:
Compose 是一个用于定义和运行多容器 Docker 应用的工具。使用 Compose,您可以使用 YAML 文件配置应用的服务。然后,只需一个命令,即可根据配置创建并启动所有服务。
该过程是使用 Dockerfiles 为每个服务定义指令,然后使用 Docker Compose 一起运行所有这些容器并促进它们之间的网络通信。
在本教程中,我们将把 Node 应用连接到 PostgreSQL 数据库。当然,在连接之前,我们需要先建立数据库容器。
添加数据库
与 Node 类似,Docker Hub 也提供了超级简单易用的PostgreSQL镜像。当然,这里也提供了 MySQL、Mongo、Redis 等等的镜像。如果你愿意,完全可以替换成你喜欢的镜像(不过,如果你还是 Docker 新手,我建议你先阅读教程)。
我们在 Docker Hub 上搜索Postgres官方镜像。我们不需要任何超出最低要求的配置,因此我们再次选择在 Alpine 上运行的版本postgres:14.1-alpine
。
与 Node 镜像不同,我们不需要复制任何文件或运行任何安装脚本,因此实际上我们不需要为 PostgreSQL 安装创建 Dockerfile。虽然我们确实需要一些配置(例如密码和端口),但我们可以使用即将发布的docker-compose.yml
文件来管理它们。
因此,除了决定要使用哪个图像之外,在创建配置文件之前我们实际上不需要做任何其他事情。
将应用程序连接到数据库
在我们创建 Docker Compose 配置文件来链接数据库容器之前,我们需要更新我们的应用程序以实际使用它。
我们的目标是创建一个包含一些非常简单的数据(例如员工列表)的数据库,用一些示例数据查看它,然后使用我们的 Node 应用程序查询该数据。
我们还将创建一个简单的前端来显示该数据。
首先我们需要安装 PostgreSQL NPM 包:
npm install pg
接下来,我们将创建一个.sql
文件,该文件会自动向数据库中填充一些可供读取的示例数据。在项目的根目录中创建以下文件:
database-seed.sql
CREATE TABLE employees
(
id SERIAL,
name text,
title text,
CONSTRAINT employees_pkey PRIMARY KEY (id)
);
INSERT INTO employees(name, title) VALUES
('Meadow Crystalfreak ', 'Head of Operations'),
('Buddy-Ray Perceptor', 'DevRel'),
('Prince Flitterbell', 'Marketing Guru');
(请注意,这些荒谬的名字是我从“异想天开”设置的随机名称生成器中得到的)
接下来,我们更新 Node 服务器来查询这些值。除此之外,我们还将使用 saexpress.static
来提供整个目录,而不仅仅是将 HTML 以 sa 字符串的形式发送。这将使我们能够提供 HTML 文件以及一些 CSS 和 JavaScript,从而创建一个功能齐全的前端。
添加评论来解释所有新内容:
server.js
// Import the postgres client
const { Client } = require("pg");
const express = require("express");
const app = express();
const port = 8080;
// Connect to our postgres database
// These values like `root` and `postgres` will be
// defined in our `docker-compose-yml` file
const client = new Client({
password: "root",
user: "root",
host: "postgres",
});
// Serves a folder called `public` that we will create
app.use(express.static("public"));
// When a GET request is made to /employees
// Our app will return an array with a list of all
// employees including name and title
// this data is defined in our `database-seed.sql` file
app.get("/employees", async (req, res) => {
const results = await client
.query("SELECT * FROM employees")
.then((payload) => {
return payload.rows;
})
.catch(() => {
throw new Error("Query failed");
});
res.setHeader("Content-Type", "application/json");
res.status(200);
res.send(JSON.stringify(results));
});
// Our app must connect to the database before it starts, so
// we wrap this in an IIFE (Google it) so that we can wait
// asynchronously for the database connection to establish before listening
(async () => {
await client.connect();
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`);
});
})();
const myPromise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("foo");
}, 300);
reject("oops");
});
myPromise.then(() => {
console.log("hello");
});
在上面的代码更新中,您可以看到我们正在提供一个public
尚未创建的目录。该目录将包含一个index.html
文件,作为我们应用程序美观的前端。
添加前端
我们首先创建public
由我们的 Node 应用程序提供服务的目录:
mkdir public
然后添加以下文件:
public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>My Docker Template</title>
<script src="script.js"></script>
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<template>
<div class="card">
<img src="https://res.cloudinary.com/dqse2txyi/image/upload/v1639943067/blogs/docker-node/profile-picture_eav2ff.png" alt="Avatar" width="240px" />
<div class="container">
<h4>Placeholder</h4>
<p>Placeholder</p>
</div>
</div>
</template>
</body>
</html>
我们的index.html
文件利用员工卡的HTML 模板。
public/styles.css
body {
padding: 12px;
display: flex;
flex-direction: row;
column-gap: 24px;
}
.card {
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);
transition: 0.3s;
border-radius: 5px;
transition: 0.3s;
}
.card:hover {
transform: scale(1.03);
}
.container {
padding: 0 12px;
}
img {
border-radius: 5px 5px 0 0;
}
上面styles.css
是一些简单的 CSS,使员工卡模板看起来整洁,并将它们在页面上排成一排。
public/script.js
fetch("/employees")
.then((response) => response.json())
.then((data) => {
data.forEach((employee) => {
// Select the <template> we created in index.html
const cardTemplate = document.querySelector('template');
// Clone a copy of the template we can insert in the DOM as a real visible node
const card = cardTemplate.content.cloneNode(true);
// Update the content of the cloned template with the employee data we queried from the backend
card.querySelector('h4').innerText = employee.name;
card.querySelector('p').innerText = employee.title;
// Append the card as a child with the employee data to the <body> element on our page
document.body.appendChild(card);
});
});
当我们的应用程序加载时,它将script.js
使用浏览器获取 API来查询/employees
我们 Node 服务器上的路由并从 PostgreSQL 数据库中获取员工信息。
一旦返回,它将遍历每个员工并克隆我们定义的 HTML 模板,以使用该员工的和index.html
制作自定义员工卡。name
title
呼!现在我们的应用程序已经建立并准备从数据库读取数据,我们终于可以使用 Docker Compose 将我们的 Node 容器和 PostgreSQL 容器连接在一起了。
创建 Docker Compose YML 文件
有关撰写的简要介绍请参见此处,有关撰写文件规范的详细信息请参见此处。
我们将创建一个简单的docker-compose.yml
文件,将我们的 Node 应用与 PostgreSQL 数据库链接起来。让我们直接在项目根目录中创建这个文件。我会用大量的注释来解释所有内容:
docker-compose.yml
version: '3.8'
services:
# These are the configurations for our Node app
# When Docker Compose starts this container it will automatically
# use the Dockerfile in the directory to configure it
app:
build: .
depends_on:
# Our app does not work without our database
# so this ensures our database is loaded first
- postgres
ports:
- "8080:8080"
volumes:
# Maps our current project directory `.` to
# our working directory in the container
- ./:/usr/src/app/
# This is the configuration for our PostgreSQL database container
# Note the `postgres` name is important, in out Node app when we refer
# to `host: "postgres"` that value is mapped on the network to the
# address of this container.
postgres:
image: postgres:14.1-alpine
restart: always
environment:
# You can set the value of environment variables
# in your docker-compose.yml file
# Our Node app will use these to connect
# to the database
- POSTGRES_USER=root
- POSTGRES_PASSWORD=root
- POSTGRES_DB=root
ports:
# Standard port for PostgreSQL databases
- "5432:5432"
volumes:
# When the PostgreSQL container is started it will run any scripts
# provided in the `docker-entrypoint-initdb.d` directory, this connects
# our seed file to that directory so that it gets run
- ./database-seed.sql:/docker-entrypoint-initdb.d/database-seed.sql
因此,有了这个docker-compose.yml
文件,我们终于可以运行包含后端、前端和数据库的全新且高度改进的应用程序“套件”了。
从项目的根目录中,您只需输入:
docker-compose up --build
(请注意,该--build
标志用于强制 Docker 在运行时重建映像,docker-compose up
以确保捕获任何新的更改。如果您只是想重新启动未更改的现有容器,则可以省略它)
激活后,你终于可以测试一下了。在我们的docker-compose.yml
配置中,我们将 8080 端口直接映射到 8080,因此请访问http://localhost:8080查看:
可爱的悬浮过渡效果,一切就绪!恭喜!
如果您使用 Docker Desktop GUI 应用程序,您将有很多选项可以一次性停止所有容器,或者单独查看每个容器。如果您使用命令行,您可以使用这个简单的命令同时停止两个容器(从项目根目录运行以获取上下文):
docker-compose down
至此,一个全栈 Node.js 应用程序就完成了,它自带了 SQL 数据库。现在,你可以将它部署到任何安装了 Docker 的地方,而且你确信它会正常工作,因为你已经定义了它运行所需的所有环境参数。
添加 pgAdmin 面板(奖励)
对于使用 PostgreSQL 的用户来说,这里有一个小福利。将 pgAdmin 面板容器添加到此应用程序设置中非常简单。只需更新您的docker-compose.yml
配置以包含以下内容:
docker-compose.yml
version: '3.8'
services:
app:
build: .
depends_on:
# Our app does not work without our database
# so this ensures our database is loaded first
- postgres
ports:
- "8080:8080"
volumes:
# Maps our current project directory `.` to
# our working directory in the container
- ./:/usr/src/app/
# This is the configuration for our PostgreSQL database container
# Note the `postgres` name is important, in out Node app when we refer
# to `host: "postgres"` that value is mapped on the network to the
# address of this container.
postgres:
image: postgres:14.1-alpine
restart: always
environment:
# You can set the value of environment variables
# in your docker-compose.yml file
# Our Node app will use these to connect
# to the database
- POSTGRES_USER=root
- POSTGRES_PASSWORD=root
- POSTGRES_DB=root
ports:
# Standard port for PostgreSQL databases
- "5432:5432"
volumes:
# When the PostgresSQL container is started it will run any scripts
# provided in the `docker-entrypoint-initdb.d` directory, this connects
# our seed file to that directory so that it gets run
- ./database-seed.sql:/docker-entrypoint-initdb.d/database-seed.sql
pgadmin-compose:
image: dpage/pgadmin4
environment:
PGADMIN_DEFAULT_EMAIL: "placeholder@example.com"
PGADMIN_DEFAULT_PASSWORD: "fakepassword123!"
ports:
- "16543:80"
depends_on:
- postgres
注意底部添加的 pgAdmin 面板配置。
当您docker-compose up --build
现在运行并转到:
您将看到 pgAdmin 面板。输入文件中的凭据PGADMIN_DEFAULT_EMAIL
即可访问它。PGADMIN_DEFAULT_PASSWORD
docker-compose.yml
进入后单击Add New Server
。
选择General -> Name
一个名字。可以是任何你想要的名字。
选项卡上的Connection
值必须与docker-compose.yml
文件匹配:
- 主持人:
postgres
- 用户名:
root
- 密码:
root
现在您可以从左侧栏导航:
Servers -> whatever-you-want -> Databases -> root -> Schemas -> public -> Tables -> employees
右键单击employees
查询工具:
SELECT * FROM employees;
查看您的数据。
有用的 Docker 命令
列出所有容器、图像、卷或网络,例如docker image ls
。
docker {container}/{image}/{volume}/{network} ls
删除容器、图像、卷或网络,其中 ID 是容器/图像/卷或网络的 ID。
docker {container}/{image}/{volume}/{network} rm ID
在后台启动一个容器(作为守护进程):
docker run -d IMAGE_ID
查看容器的日志:
docker container logs CONTAINER_ID
查看容器的信息:
docker container inspect CONTAINER_ID
在活动容器内打开一个 shell,以便您可以在其中运行终端命令。
docker exec -it CONTAINER_ID /bin/sh
停止容器:
docker container stop CONTAINER_ID
删除所有悬空/未使用的 Docker 数据(缓存层、不再使用的卷等):
docker system prune
您还可以将上述命令与特定类型一起使用,例如docker container prune
。
总结
我希望你了解了 Docker 为何是你工具箱中一款出色的工具,以及如何使用它来减少与设置开发环境相关的麻烦。值得庆幸的是,与 WAMP、MAMP 和 XAMPP 斗争的日子已经过去很久了(我不是轻视这些应用程序,我知道只要配置得当,它们确实是非常棒的工具)。
请记住,Docker 既可以在许多不同开发人员的机器上创建基准标准开发环境,也可以在生产环境中使用,只需部署更多容器即可简化应用程序的扩展流程,从而应对流量增长。
除了这里介绍的内容之外,还有很多值得学习的内容,Docker 文档是最好的起点。祝您 Docker 之旅一切顺利。
请查看我的其他学习教程。如果您觉得其中有任何内容有用,请随时发表评论或提出问题并与他人分享:
想要了解更多类似教程,请在 Twitter 上关注我@eagleson_alex
文章来源:https://dev.to/alexeagleson/docker-for-javascript-developers-41me