介绍
Docker 是一个容器平台,它是一个轻量级、虚拟化、可移植、软件定义的标准化环境。它允许软件与运行在物理宿主机上的其他软件隔离运行。Docker 是软件开发中持续开发和集成方面的一个决定性组件。它提供了一个 虚拟机的轻量级替代方案,并允许开发人员享受分布式应用架构。为了深入了解 Docker 生态系统的概览,请查看这篇文章.
使用 Docker 构建应用程序的过程始于开发人员为其应用程序创建镜像。然后,该镜像将被部署在容器中。镜像包含应用程序的定义组件,例如应用程序代码、库、配置文件、环境变量和运行时环境。镜像标准化了容器内部的环境,从而赋予了容器化可移植性的特征。 Node.js 是一个开源、跨平台的后端 JavaScript 运行时环境,可以在浏览器之外执行 JavaScript 代码。它构建在 Chrome 的 V8 JavaScript 引擎. Express.js 是一个运行在 Node.js 之上的极简后端 JavaScript 框架。
在本教程中,我们将为运行在 Express 框架上的网站创建一个镜像。我们将使用 Bootstrap(一个前端库)来让前端看起来更好看。创建镜像后,我们将构建一个容器并将其推送到 Docker Hub。Docker Hub 允许开发人员托管容器化应用程序,以便轻松部署到任何 Docker 环境。一旦您的容器托管在 Docker Hub 上,我们将拉取它并构建另一个实际用于运行我们网站的镜像。
前提条件
这是一个动手实践教程。您应该创建一个能够让您跟着操作的环境。
- 您应该已经 安装了 Ubuntu 20.04 作为您的初始运行环境,并 创建了一个具有 sudo 特权的非 root 用户。使用该非 root 用户登录并继续以下步骤。
- 您需要安装 Docker。请按照我们关于 如何在 Ubuntu 上安装和操作 Docker 的教程中的步骤 1、2、3 和 4 进行操作。这应该适用于任何 Ubuntu 发行版。
- 如果您还没有 Docker Hub 帐户,请创建一个。您可以访问此链接获取 Docker Hub 快速入门指南.
- 安装 Node.js 和 NPM。NPM 是一个 JavaScript 包管理器。您可以参考 这些关于安装 Node 和 npm 的说明.
步骤 1:配置应用程序依赖项
在创建镜像之前,您需要创建应用程序源代码。应用程序源代码包括将被复制到容器中的代码、静态内容和依赖项。首先在非 root 用户的家目录中为您的项目创建一个目录。我们将其命名为 node_express,但您也可以自由使用您喜欢的目录名称:
|
1 |
mkdir node_express |
接下来,进入该目录:
|
1 |
cd node_express |
这将是您的应用程序根目录。一个 node.js 应用程序需要根目录中包含一个 package.json 文件。Npm 使用此文件来确定您的应用程序需要哪些依赖项。输入以下命令来创建此文件:
|
1 |
nano package.json |
之后,将以下代码片段添加到文件中。您可以根据需要更新名称、作者、描述和入口点文件:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
{ "name": "node-express-docker-image", "version": "1.0.0", "description": "Nodejs Express Docker Image Example", "author": "hackins", "main": "index.js", "license": "ISC", "keywords": [ "nodejs", "express", "bootstrap" ], "dependencies": { "express": "^4.17.1" } } |
如您所见,此文件指定了项目名称、版本、作者以及共享应用程序代码所依据的许可协议。建议为您的项目使用简短且具有描述性的名称,以避免在 npm 注册表 中出现重复。我们为该项目指定了 ISC 许可协议,它允许免费复制、修改或分发应用程序代码。
最重要的是,您应该注意文件中的以下指令:
- “
main”:此指令指定了应用程序的入口点,我们将其设置为 index.js。我们稍后将创建此文件。 - “
dependencies”:此指令指定了当我们运行npm命令时将从 npm 注册表拉取的应用程序依赖项,在我们的案例中,我们需要 Express 4.17.1 及以上版本。
现在您可以通过按 Ctrl + O 保存文件。然后,通过按 Ctrl + X 关闭文件。接下来,我们将通过运行以下命令来安装依赖项:
|
1 |
npm install |
该命令会安装 package.json 文件中指定的应用程序依赖项,并将其放入 node_modules 目录中。这些目录在您首次运行该命令时已自动创建。安装好应用程序依赖项后,您现在可以开始添加应用程序代码了。
第 2 步:添加您的应用程序代码文件
我们将创建一个基础的食谱网站,这要感谢 allrecipes。该应用程序的主要入口点是 index.js 文件。我们将添加一个 views 目录,用于存放项目的各种页面和静态资源。该网站将有一个引导页,其中包含介绍性信息和一些食谱的链接。
我们的引导页代码将放在 home.html 文件中。首先,通过输入以下命令创建 index.js 文件:
|
1 |
nano index.js |
添加以下代码,该代码导入并创建了一个 Express 应用程序。它还指定了 Router 对象、基础目录以及提供此应用程序服务的端口:
|
1 2 3 4 5 6 |
const express = require('express'); const app = express(); const router = express.Router(); const path = __dirname + '/views/'; const port = 8090; |
require 是一个加载模块的 JavaScript 函数。在这种情况下,我们正在加载 express 模块。然后,我们将使用导入的模块来创建 express 和 router 对象。随着教程的深入,router 对象将通过响应添加到该对象中的 HTTP 方法调用来执行应用程序的路由功能。
我们还设置了 path 和 port。path 常量定义了代码的基础目录。在我们的案例中,它是项目根目录下的 views 子目录。 port 指定了 express 应用程序应该监听的端口,在我们的示例中,我们将其设置为 8090.
一旦我们有了这些常量,我们就可以使用 router 对象为应用程序指定一些路由。将以下代码添加到 index.js 文件中以指定路由:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
router.use(function (req,res,next) { console.log('/' + req.method); next(); }); router.get('/', function(req,res){ res.sendFile(path + 'home.html'); }); router.get('/lasagna', function(req,res){ res.sendFile(path + 'lasagna.html'); }); router.get('/guacamole', function(req,res){ res.sendFile(path + 'guacamole.html'); }); router.get('/banana-bread', function(req,res){ res.sendFile(path + 'banana_bread.html'); }); |
您可以 添加中间件 到路由,通过使用 router.use 函数。在这种情况下,我们添加了一个函数,在将路由请求传递给应用程序路由之前对其进行记录。对应用程序根目录的 GET 请求将返回 home.html 页面。然后,我们为三个食谱添加了页面,这些页面也将通过对特定食谱页面的 GET 请求来检索。
最后,添加以下代码以挂载 router 中间件和应用程序静态资源。此外,告诉 express 应用程序监听端口 8090:
|
1 2 3 4 5 6 |
app.use(express.static(path)); app.use('/', router); app.listen(port, function () { console.log('Nodejs Express Example App listening on port ' + port) }) |
您完整的 index.js 文件应该像这样:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
const express = require('express'); const app = express(); const router = express.Router(); const path = __dirname + '/views/'; const port = 8090; router.use(function (req,res,next) { console.log('/' + req.method); next(); }); router.get('/', function(req,res){ res.sendFile(path + 'home.html'); }); router.get('/lasagna', function(req,res){ res.sendFile(path + 'lasagna.html'); }); router.get('/guacamole', function(req,res){ res.sendFile(path + 'guacamole.html'); }); router.get('/banana-bread', function(req,res){ res.sendFile(path + 'banana_bread.html'); }); app.use(express.static(path)); app.use('/', router); app.listen(port, function () { console.log('Nodejs Express Example App listening on port ' + port) }) |
您现在可以保存并关闭该文件。下一步是将静态网页添加到 views 目录。首先输入以下命令来创建该目录:
|
1 |
mkdir views |
输入以下命令以打开 home.html 落地页文件:
|
1 |
nano views/home.html |
将以下代码添加到文件中。该代码导入了 Bootstrap,并向网站访问者提供一些关于该网站主题的信息:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="description" content=""> <meta name="author" content="Mark Otto, Jacob Thornton, and Bootstrap contributors"> <meta name="generator" content="Hugo 0.80.0"> <title>美味食谱</title> <!-- Bootstrap 核心 CSS --> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta2/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous"> <link href="css/custom.css" rel="stylesheet"> </head> <body> <nav class="navbar navbar-dark bg-dark navbar-static-top navbar-expand-md"> <div class="container"> <button type="button" class="navbar-toggler collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false"> <span class="sr-only">切换导航</span> </button> <a class="navbar-brand" href="#">美味食谱</a> <div class="collapse navbar-collapse justify-content-center" id="bs-example-navbar-collapse-1"> <ul class="nav navbar-nav justify-content-center"> <li class="active nav-item"> <a href="/" class="nav-link">首页</a> </li> <li class="nav-item"> <a href="/lasagna" class="nav-link">千层面</a> </li> <li class="nav-item"> <a href="/guacamole" class="nav-link">牛油果酱</a> </li> <li class="nav-item"> <a href="/banana-bread" class="nav-link">香蕉面包</a> </li> </ul> </div> </div> </nav> <main> <section class="py-5 text-center container"> <div class="row py-lg-5"> <div class="col-lg-6 col-md-8 mx-auto"> <h1 class="fw-light">美味食谱</h1> <p class="lead text-muted"> 从这些极佳的食谱中寻找并分享每日烹饪灵感。发现食谱、厨师、 视频以及基于您喜爱的食物和关注的朋友的指南。 <br /> <em>(别太认真,这只是用于我们的演示 node-express-docker 镜像应用)</em> </p> </div> </div> </section> </main> </body> </html> |
除了导入 Bootstrap 之外,该页面还添加了一个基本的 导航菜单 ,以帮助我们在页面之间跳转并返回到主页。我们还添加了一行来导入我们的自定义 CSS 文件:
|
1 |
<link href="css/custom.css" rel="stylesheet"> |
稍后我们将使用此文件为应用程序添加自定义样式。现在,让我们为食谱创建三个页面。我们首先从创建 千层面 页面开始。使用以下命令用 nano 编辑器打开该文件:
|
1 |
nano views/lasagna.html |
在打开的文件中,添加以下代码。此文件将导入 Bootstrap、custom.css 文件,指定一个导航菜单并提供一些千层面食谱信息:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="description" content=""> <meta name="author" content="Mark Otto, Jacob Thornton 和 Bootstrap 贡献者"> <meta name="generator" content="Hugo 0.80.0"> <title>千层面食谱</title> <!-- Bootstrap 核心 CSS --> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta2/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous"> <link href="css/custom.css" rel="stylesheet"> </head> <body> <nav class="navbar navbar-dark bg-dark navbar-static-top navbar-expand-md"> <div class="container"> <button type="button" class="navbar-toggler collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false"> <span class="sr-only">切换导航</span> </button> <a class="navbar-brand" href="#">极棒食谱</a> <div class="collapse navbar-collapse justify-content-center" id="bs-example-navbar-collapse-1"> <ul class="nav navbar-nav justify-content-center"> <li class="active nav-item"> <a href="/" class="nav-link">首页</a> </li> <li class="nav-item"> <a href="/lasagna" class="nav-link">千层面</a> </li> <li class="nav-item"> <a href="/guacamole" class="nav-link">牛油果酱</a> </li> <li class="nav-item"> <a href="/banana-bread" class="nav-link">香蕉面包</a> </li> </ul> </div> </div> </nav> <main> <section class="py-5 text-center container bg-light"> <div class="row py-lg-5"> <div class="col-lg-6 col-md-8 mx-auto"> <h1 class="fw-light">最棒的千层面食谱</h1> <p class="lead text-muted"> 这是您能做出的最棒的千层面。 <br /> <em>(别太当真,这只是我们演示用的 node-express-docker 镜像应用)</em> </p> <h3>配料</h3> <ul class="list-group"> <li class="list-group-item">1 磅甜意式香肠</li> <li class="list-group-item">¾ 磅瘦牛肉碎</li> <li class="list-group-item">½ 杯洋葱碎</li> <li class="list-group-item">2 瓣大蒜,压碎</li> <li class="list-group-item">1 罐(28 盎司)碎番茄</li> <li class="list-group-item">2 罐(6 盎司)番茄膏</li> <li class="list-group-item">2 罐(6.5 盎司)罐装番茄沙司</li> <li class="list-group-item">½ 杯水</li> <li class="list-group-item">2 汤匙白糖</li> <li class="list-group-item">1 ½ 茶匙干罗勒叶</li> <li class="list-group-item">½ 茶匙茴香籽</li> <li class="list-group-item">1 茶匙意式调味料</li> <li class="list-group-item">1 ½ 茶匙盐,分次使用,或按个人口味调整</li> <li class="list-group-item">¼ 茶匙黑胡椒粉</li> <li class="list-group-item">4 汤匙切碎的新鲜欧芹</li> <li class="list-group-item">12 片千层面面片</li> <li class="list-group-item">16 盎司里科塔奶酪</li> <li class="list-group-item">1 个鸡蛋</li> <li class="list-group-item">¾ 磅马苏里拉奶酪,切片</li> <li class="list-group-item">¾ 杯磨碎的帕玛森奶酪</li> </ul> </div> </div> </section> </main> </body> </html> |
让我们按照相同的步骤为 牛油果酱 食谱页面创建一个文件。运行以下命令,使用 nano 打开该文件:
|
1 |
nano views/guacamole.html |
然后将此代码添加到文件中:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="description" content=""> <meta name="author" content="Mark Otto, Jacob Thornton, and Bootstrap contributors"> <meta name="generator" content="Hugo 0.80.0"> <title>牛油果酱食谱</title> <!-- Bootstrap 核心 CSS --> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta2/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous"> <link href="css/custom.css" rel="stylesheet"> </head> <body> <nav class="navbar navbar-dark bg-dark navbar-static-top navbar-expand-md"> <div class="container"> <button type="button" class="navbar-toggler collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false"> <span class="sr-only">切换导航</span> </button> <a class="navbar-brand" href="#">美味食谱</a> <div class="collapse navbar-collapse justify-content-center" id="bs-example-navbar-collapse-1"> <ul class="nav navbar-nav justify-content-center"> <li class="active nav-item"> <a href="/" class="nav-link">首页</a> </li> <li class="nav-item"> <a href="/lasagna" class="nav-link">千层面</a> </li> <li class="nav-item"> <a href="/guacamole" class="nav-link">牛油果酱</a> </li> <li class="nav-item"> <a href="/banana-bread" class="nav-link">香蕉面包</a> </li> </ul> </div> </div> </nav> <main> <section class="py-5 text-center container bg-light"> <div class="row py-lg-5"> <div class="col-lg-6 col-md-8 mx-auto"> <h1 class="fw-light">最佳牛油果酱食谱</h1> <p class="lead text-muted"> 您可以根据自己的口味将这款牛油果沙拉制作得细腻平滑或保留颗粒感。 <br /> <em>(没什么特别的,这只是用于我们的 demo node-express-docker 镜像应用)</em> </p> <h3>配料</h3> <ul class="list-group"> <li class="list-group-item">3 个牛油果 - 去皮、去核并捣碎</li> <li class="list-group-item">1 个青柠,榨汁</li> <li class="list-group-item">1 茶匙盐</li> <li class="list-group-item">½ 杯洋葱丁</li> <li class="list-group-item">3 汤匙切碎的新鲜香菜</li> <li class="list-group-item">2 个罗马(李子)番茄,切丁</li> <li class="list-group-item">1 茶匙蒜泥</li> <li class="list-group-item">1 夹辣椒粉(可选)</li> </ul> </div> </div> </section> </main> </body> </html> |
最后,让我们创建 banana_bread.html 文件,输入以下命令:
|
1 |
nano views/banana_bread.html |
然后,将以下 HTML 代码添加到文件中:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="description" content=""> <meta name="author" content="Mark Otto, Jacob Thornton, and Bootstrap contributors"> <meta name="generator" content="Hugo 0.80.0"> <title>香蕉面包食谱</title> <!-- Bootstrap 核心 CSS --> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta2/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous"> <link href="css/custom.css" rel="stylesheet"> </head> <body> <nav class="navbar navbar-dark bg-dark navbar-static-top navbar-expand-md"> <div class="container"> <button type="button" class="navbar-toggler collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false"> <span class="sr-only">切换导航</span> </button> <a class="navbar-brand" href="#">美味食谱</a> <div class="collapse navbar-collapse justify-content-center" id="bs-example-navbar-collapse-1"> <ul class="nav navbar-nav justify-content-center"> <li class="active nav-item"> <a href="/" class="nav-link">首页</a> </li> <li class="nav-item"> <a href="/lasagna" class="nav-link">千层面</a> </li> <li class="nav-item"> <a href="/guacamole" class="nav-link">牛油果酱</a> </li> <li class="nav-item"> <a href="/banana-bread" class="nav-link">香蕉面包</a> </li> </ul> </div> </div> </nav> <main> <section class="py-5 text-center container bg-light"> <div class="row py-lg-5"> <div class="col-lg-6 col-md-8 mx-auto"> <h1 class="fw-light">最棒的香蕉香蕉面包食谱</h1> <p class="lead text-muted"> 为什么要对香蕉风味妥协?这款香蕉面包口感湿润,美味可口,带有浓郁的香蕉风味!朋友和家人都非常喜欢我的食谱,并说这是迄今为止最棒的!烤一下更美味!!尽情享受吧! <br /> <em>(没什么大不了的,这只是用于我们的演示 node-express-docker 镜像应用)</em> </p> <h3>配料</h3> <ul class="list-group"> <li class="list-group-item">2 杯中筋面粉</li> <li class="list-group-item">1 茶匙小苏打</li> <li class="list-group-item">¼ 茶匙盐</li> <li class="list-group-item">½ 杯黄油</li> <li class="list-group-item">¾ 杯红糖</li> <li class="list-group-item">2 个鸡蛋,打散</li> <li class="list-group-item">2⅓ 杯熟透的香蕉泥</li> </ul> </div> </div> </section> </main> </body> </html> |
现在,我们已经创建了所有页面。如果你还记得,我们还要添加 css/custom.css 文件。输入以下命令来创建该目录:
|
1 |
mkdir views/css |
然后使用以下命令在 nano 编辑器中创建并打开该文件:
|
1 |
nano views/css/custom.css |
你可以根据需要添加更多 CSS 代码来美化你的网站。为简便起见,让我们将以下代码片段添加到文件中:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
.bd-placeholder-img { font-size: 1.125rem; text-anchor: middle; -webkit-user-select: none; -moz-user-select: none; user-select: none; } @media (min-width: 768px) { .bd-placeholder-img-lg { font-size: 3.5rem; } } |
完成后保存并关闭文件。
由于我们现在已经安装了应用程序源代码和项目依赖项,因此可以启动该应用程序。
我们已将应用设置为监听端口 8090,运行以下命令以指示防火墙允许流量通过该端口。如果你指定了其他端口,请替换命令中的端口号:
|
1 |
sudo ufw allow 8090 |
现在,你可以启动应用程序。但首先,请运行以下命令确保你处于项目根目录中:
|
1 |
cd ~/node_express |
使用 node index.js 启动应用程序。如果你指定了不同的入口点,请将其替换为你的入口点:
|
1 |
node index.js |
如果你在浏览器中导航到 http://your_public_server_ip:8090,你将看到定义的食谱(Recipes)着陆页:
你可以在导航中看到指向各种食谱的链接。让我们点击一些。下面是 Lasagna 食谱页面:
这里是 Guacamole 食谱页面:
至此,你已经创建了应用程序并测试了其是否按预期工作。你可以通过按 Ctrl + C 退出服务器,然后继续创建 Dockerfile。Dockerfile 有助于实现可扩展性,因为它可以在需要时重新创建应用程序实例。
第 3 步:创建 Dockerfile
Docker 在构建镜像时会读取 Dockerfile 中指定的指令。它指定了应用程序的运行环境。因此,它有助于开发人员避免依赖项不一致或运行版本更改的问题。输入以下命令来创建 Dockerfile:
|
1 |
nano Dockerfile |
Docker 镜像是使用相互构建的多个镜像层创建的。首先,您需要添加一个基础镜像,作为应用程序的起点。
由于该应用程序需要在 node.js 环境中运行,我们将首先添加 node:10-alpine image ,用于 node.js。目前,在编写本教程时,这是 recommended LTS version of Node.js。我们选择这个特定镜像是因为它源自 Alpine Linux project。因此,它将有助于将我们的镜像大小保持在最低限度。在 Docker Hub Node images page 下有几个镜像变体,您可以根据需要进行选择。
添加以下代码,使用 FROM 指令来设置应用程序的基础镜像:
|
1 |
FROM node:10-alpine |
该镜像包含 Node.js 和 npm。每个 Dockerfile 必须以 FROM 指令开始。Docker node 镜像默认带有一个非 root 的 node 用户,您可以使用它来运行您的应用程序容器,而不是以 root 身份运行。Docker security recommends 不要以 root 身份运行容器,并将权限限制为仅运行其资源所需的权限。
在这种情况下,我们将使用 node 用户的主目录作为应用程序的工作目录,并作为容器内的用户。您可以查看此 Docker Node image best practices 指南以获取更多信息。
我们将在 node_modules 子目录,连同 app 目录一起创建在 /home/node 内部,以帮助简化应用程序代码的权限。创建这些目录可以确保在我们在容器内部本地运行 npm install 命令时,它们具有正确的权限。创建目录后,您必须将它们的所有权设置为 node 用户。我们将在 Dockerfile 中通过添加以下行来实现这一点:
|
1 |
mkdir -p /home/node/app/node_modules && chown -R node:node /home/node/app |
然后,您将通过添加以下行来设置工作目录:
|
1 |
WORKDIR /home/node/app |
始终设置 WORKDIR 是一个好主意,这样 Docker 就不必默认创建它。
添加以下行以复制 package.json 和 package-lock.json 文件:
|
1 |
COPY package*.json ./ |
建议在运行 COPY 指令之前添加 npm install 或复制应用程序源代码。这使您能够利用 Docker 的缓存机制。在构建过程中,Docker 会检查是否为每个指令缓存了一个层。这意味着如果您没有更改 package.json 文件,那么 Docker 将使用现有的镜像层,并避免重新安装 node modules,从而加快构建过程。
在运行 npm install 之前,添加以下行以将用户切换为 node,以确保所有应用程序文件和 node_modules 目录都归非 root 的 node 用户所有:
|
1 |
USER node |
我们的容器现在已准备好运行 npm install 命令。将以下行添加到 Dockerfile 中:
|
1 |
RUN npm install |
一旦安装了 node_modules,添加以下行,这将指示 Docker 将应用程序代码复制到容器上的应用程序目录中,并具有正确的权限和所有权,即非 root 的 node 用户:
|
1 |
COPY --chown=node:node . . |
最后一步是公开容器上的端口 8090,正如我们在入口 index.js 文件中所定义的那样:
|
1 2 |
EXPOSE 8090 CMD [ "node", "index.js" ] |
EXPOSE 设置容器上的哪些端口将在运行时打开。 CMD 运行启动应用程序的命令,在本例中为 node index.js。
Dockerfile 中只能有一个 CMD 命令,因为只有最后一个才会生效。请查看Dockerfile 参考文档,以获取您可以使用 Dockerfile 执行的操作列表。
您完整的 Dockerfile 应该像这样:
|
1 2 3 4 5 6 7 8 9 |
FROM node:10-alpine RUN mkdir -p /home/node/app/node_modules && chown -R node:node /home/node/app WORKDIR /home/node/app COPY package*.json ./ USER node RUN npm install COPY --chown=node:node . . EXPOSE 8090 CMD [ "node", "index.js" ] |
您现在可以保存并关闭该文件。
接下来您要做的就是添加 .dockerignore 文件。就像 .gitignore 文件一样,.dockerignore 指定了项目目录中哪些文件和目录不应该被复制到容器中。
使用 nano 编辑器打开文件:
|
1 |
nano .dockerignore |
在文件内添加以下行:
|
1 2 3 4 |
node_modules npm-debug.log Dockerfile .dockerignore |
如果使用的是 git 仓库,那么您还应该添加 .git 目录和 .gitignore 文件。保存并关闭文件。
如果一切顺利,现在是时候使用 docker build 命令来构建应用程序镜像了。您可以将 –t 标志添加到 docker build 命令中,以便为镜像标记一个易于记忆的名称,而不是 docker 默认设置的随机字符串。我们还将把镜像推送到 Docker Hub,因此最好在标签中包含您的 Docker Hub 用户名。
我们将使用 nodejs-express-image 作为标签名称。您可以自由选择自己喜欢的标签名称。以下是构建镜像的命令:
|
1 |
sudo docker build -t your_dockerhub_username/nodejs-express-image . |
请记住将 your_dockerhub_username 替换为您实际的 Docker Hub 用户名。末尾的 .(点)指定构建上下文为当前目录。
构建过程需要一到两分钟。完成后,输入命令以检查您的镜像:
|
1 |
sudo docker images |
您应该会看到类似以下的内容:
请记住,我们已将 your_dockerhub_username 替换为实际的用户名。
确认镜像已构建后,您现在可以使用 docker run 创建一个带有该镜像的容器。将包含以下标志:
-p:发布容器上的端口并将其映射到主机系统上的端口。出于演示目的,我们将在主机系统上使用端口 80。但是,如果您在该端口上运行了另一个进程,请根据需要进行修改。了解更多关于 Docker 文档中的端口绑定.-d:用于后台模式。允许容器在后台继续运行。--name:您可以使用它来设置一个易于记忆的名称,而不是让 Docker 分配一个随机字符串。
构建容器的命令如下。请适当替换您的 Docker Hub 用户名:
|
1 |
sudo docker run --name nodejs-express-image -p 80:8090 -d your_dockerhub_username/nodejs-express-image |
等待容器构建并开始运行。您可以使用此命令检查所有正在运行的容器:
|
1 |
sudo docker ps |
您应该会看到类似于以下内容的输出:
如输出所示,容器现在正在运行。如果您在浏览器中访问服务器的公共 IP 地址(不带端口),则可以在浏览器中查看它。您的首页将会加载:
您已成功使用 Docker 部署了 Node Express 静态网站。让我们看看如何将此镜像推送到 Docker Hub,以便将来使用和扩展。
步骤 4:使用 Docker 镜像仓库
您可以将镜像推送到像 Docker Hub 这样的镜像仓库中,并保存它们以备将来使用、与其他开发人员共享或允许扩展您的容器。我们可以将创建的镜像推送到 Docker Hub,并使用它来重新创建容器。
使用以下命令登录您的 Docker Hub 帐户。将其替换为您实际的 Docker Hub 用户名:
|
1 |
sudo docker login -u your_dockerhub_username |
在提示时输入您的密码。登录后,将在您的用户主目录下创建一个 ~/.docker/config.json 文件,其中包含您的 Docker Hub 凭据。
设置完成后,输入以下命令将镜像推送到 Docker Hub,并指定您之前构建镜像时设置的标签:
|
1 |
sudo docker push your_dockerhub_username/nodejs-express-image |
此命令将 docker 镜像推送到您的 Docker Hub 帐户。如果您访问您的帐户,您可以看到您最近推送的镜像:
我们可以通过销毁当前的应用程序容器并使用仓库中的镜像重新构建它,来测试镜像仓库的实用性。
输入以下命令列出您当前的容器:
|
1 |
sudo docker ps |
您应该会看到类似于以下的输出:
注意输出中列出的 CONTAINER ID,复制它,并使用以下命令停止您的容器,将 ID 替换为您的 ID:
|
1 |
sudo docker stop 1bb2d65279bb |
输入以下命令列出您系统中所有可用的 docker 镜像:
|
1 |
sudo docker images –a |
输出将显示您的镜像名称、node.js 镜像以及构建过程中的其他镜像。
输入以下命令删除镜像,包括未使用的或悬空的镜像:
|
1 |
sudo docker system prune |
输入 y 以确认。这将删除已停止的容器和镜像。如果您列出它们,您将在输出中看到一个空列表:
现在,您已经删除了运行应用程序的容器以及镜像本身。通过阅读我们的教程,了解更多关于删除 Docker 容器、镜像和数据卷.
我们现在可以通过首先使用以下命令从 Docker Hub 拉取镜像来重新创建整个过程。请相应地替换您的 Docker Hub 用户名:
|
1 |
sudo docker pull your_dockerhub_username/nodejs-express-image |
再次使用以下命令列出您的 Docker 镜像:
|
1 |
sudo docker images |
您应该在输出中看到该镜像:
您现在可以使用来自 步骤 3 的命令重新构建您的容器。当然,请在适当的地方替换您的 Docker Hub 用户名:
|
1 |
sudo docker run --name nodejs-express-image -p 80:8090 -d your_dockerhub_username/nodejs-express-image |
列出您的容器以确认它已被重新构建:
|
1 |
sudo docker ps |
您应该会看到类似的输出:
在浏览器中,导航到您服务器的公网 IP 地址,您应该会看到您的应用程序正在运行。
结论
如果您已经按照教程进行到这一步,那么您现在已经拥有了一个使用 Express 和 Bootstrap 制作并使用 Docker 部署的静态网站。您使用静态网站文件构建了 Docker 镜像,并使用该镜像创建了容器。然后,您将镜像推送到 Docker 镜像仓库 Docker Hub,使其可用于将来使用或扩展。为了测试镜像仓库的使用,您销毁了镜像和容器,从仓库中拉取了镜像,并重新构建了容器。
本教程解释了如何部署 Node.js 应用程序。如果您想了解如何使用不同的 Web 开发技术栈,我们有一篇关于在 Nginx 上使用 Docker Compose 部署 Laravel 应用程序.
有关利用 Docker 的更多资源,请查看以下教程:
祝您计算愉快!











评论
暂无评论。发表第一条评论吧。