使用Docker构建Node.js应用的详细指南

引言

Docker平台允许开发者将应用程序打包并运行为容器。容器是一个在共享操作系统上运行的隔离进程,提供了一种比虚拟机更轻量级的替代方案。尽管容器并不是新事物,但它们提供的好处——包括进程隔离和环境标准化——随着越来越多的开发者使用分布式应用程序架构,这些好处的重要性日益增加。

在本教程中,你将为一个使用Express框架和Bootstrap的静态网站创建应用程序镜像。然后,你将使用该镜像构建一个容器,并将其推送到Docker Hub以备将来使用。最后,你将从你的Docker Hub仓库中拉取存储的镜像,并构建另一个容器,演示你如何重现和扩展你的应用程序。

前提条件

要遵循本教程,你将需要:

需要一个已设置的Ubuntu服务器,具有非root用户,具有sudo权限,并启用了防火墙以阻止非必要的端口。

设置完成后,以的非root用户身份登录并进行第一步。

建议使用至少有以下配置的服务器:

4 核心的 CPU,4GB 的内存

选择服务器提供商
为了本教程的演示,我将以一个具体的云服务提供商为例,展示如何进行操作。选择哪个提供商根据个人偏好和需求来决定。

以下步骤仅供参考,请根据实际需求选择配置。

购买云服务器
本示例中,我们选择了香港作为服务器区域。

点击 云产品云服务器立即购买

选择操作系统
在创建服务器实例时,选择 Ubuntu 作为操作系统。

连接到服务器
使用 X-shell 或偏好的 SSH 客户端,通过远程用户名和密码连接到服务器。成功连接后,将看到特定的欢迎信息,表明已成功登录。

https://syxoss.oss-cn-hangzhou.aliyuncs.com/Typora202411211021991.png

  • 在你的服务器上安装了Docker
  • 安装了Node.js和npm
  • 一个Docker Hub账户

使用Docker构建NodeJS应用程序的步骤

  1. 安装应用程序依赖项
  2. 创建NodeJS应用程序文件
  3. 编写Dockerfile
  4. 创建DockerHub镜像仓库

第1步 — 安装你的应用程序依赖项

要创建你的镜像,你首先需要制作你的应用程序文件,然后你可以将它们复制到你的容器中。这些文件将包括你的应用程序的静态内容、代码和依赖项。

首先,在你的非root用户的主目录中为你的项目创建一个目录。我们将我们的项目目录命名为node_project,但你可以用其他名称替换:

mkdir node_project

导航到这个目录:

cd node_project

这将是项目的根目录。

接下来,创建一个package.json文件,包含你的项目依赖项和其他识别信息。用nano或你喜欢的编辑器打开文件:

nano package.json

添加以下关于项目的信息,包括它的名称、作者、许可证、入口点和依赖项。确保将作者信息替换为你自己的姓名和联系详情:

~/node_project/package.json

{
  "name": "nodejs-image-demo",
  "version": "1.0.0",
  "description": "nodejs image demo",
  "author": "Sammy the Shark <sammy@example.com>",
  "license": "MIT",
  "main": "app.js",
  "keywords": [
    "nodejs",
    "bootstrap",
    "express"
  ],
  "dependencies": {
    "express": "^4.16.4"
  }
}

这个文件包括了项目名称、作者和共享许可证。npm建议使你的项目名称简短且描述性,并避免在npm注册表中重复。我们在许可证字段中列出了MIT许可证,允许自由使用和分发应用程序代码。

此外,文件指定了:

  • "main": 应用程序的入口点,app.js。你将在下一步创建这个文件。
  • "dependencies": 项目依赖项——在这种情况下,是Express 4.16.4或更高版本。

尽管这个文件没有列出仓库,你可以通过遵循这些指南将仓库添加到你的package.json文件中。如果你正在对应用程序进行版本控制,这是一个好的补充。

完成更改后保存并关闭文件。

要安装项目依赖项,请运行以下命令:

npm install

这将在你的项目目录中安装你在package.json文件中列出的包。

现在我们可以继续构建应用程序文件。

第2步 — 创建应用程序文件

我们将创建一个网站,为用户提供有关鲨鱼的信息。我们的应用程序将有一个主入口点app.js,以及一个views目录,其中将包括项目的静态资产。登陆页面index.html将为用户提供一些初步信息和一个链接到更详细的鲨鱼信息页面sharks.html。在views目录中,我们将创建登陆页面和sharks.html

首先,在主项目目录中打开app.js定义项目的路由:

nano app.js

文件的第一部分将创建Express应用程序和Router对象,并定义基目录和端口作为常量:

~/node_project/app.js

const express = require('express');
const app = express();
const router = express.Router();

const path = __dirname + '/views/';
const port = 8080;

require函数加载了express模块,然后我们用它来创建approuter对象。router对象将执行应用程序的路由功能,我们定义HTTP方法路由时,将它们添加到这个对象中,以定义我们的应用程序如何处理请求。

这部分文件还设置了两个常量,pathport

  • path: 定义了基目录,它将是当前项目目录中的views子目录。
  • port: 告诉应用程序监听并绑定到端口8080

接下来,使用router对象设置应用程序的路由:

~/node_project/app.js

...

router.use(function (req,res,next) {
  console.log('/' + req.method);
  next();
});

router.get('/', function(req,res){
  res.sendFile(path + 'index.html');
});

router.get('/sharks', function(req,res){
  res.sendFile(path + 'sharks.html');
});

router.use函数加载了一个中间件函数,它将记录路由器的请求并将它们传递给应用程序的路由。这些在随后的函数中定义,指定基本项目URL的GET请求应该返回index.html页面,而对/sharks路由的GET请求应该返回sharks.html

最后,挂载router中间件和应用程序的静态资产,并告诉应用程序监听端口8080

~/node_project/app.js

...

app.use(express.static(path));
app.use('/', router);

app.listen(port, function () {
  console.log('Example app listening on port 8080!')
})

完成的app.js文件将如下所示:

~/node_project/app.js

const express = require('express');
const app = express();
const router = express.Router();

const path = __dirname + '/views/';
const port = 8080;

router.use(function (req,res,next) {
  console.log('/' + req.method);
  next();
});

router.get('/', function(req,res){
  res.sendFile(path + 'index.html');
});

router.get('/sharks', function(req,res){
  res.sendFile(path + 'sharks.html');
});

app.use(express.static(path));
app.use('/', router);

app.listen(port, function () {
  console.log('Example app listening on port 8080!')
})

完成编辑后保存并关闭文件。

接下来,让我们为应用程序添加一些静态内容。首先创建views目录:

mkdir views

打开登陆页面文件index.html

nano views/index.html

添加以下代码,该代码将导入Bootstrap并创建一个jumbotron组件,其中包含一个链接到更详细的sharks.html信息页面:

~/node_project/views/index.html

<!DOCTYPE html>
<html lang="en">

<head>
    <title>About Sharks</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
    <link href="css/styles.css" rel="stylesheet">
    <link href="https://fonts.googleapis.com/css?family=Merriweather:400,700" rel="stylesheet" type="text/css">
</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">Toggle navigation</span>
            </button> <a class="navbar-brand" href="#">Everything Sharks</a>
            <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
                <ul class="nav navbar-nav mr-auto">
                    <li class="active nav-item"><a href="/" class="nav-link">Home</a>
                    </li>
                    <li class="nav-item"><a href="/sharks" class="nav-link">Sharks</a>
                    </li>
                </ul>
            </div>
        </div>
    </nav>
    <div class="jumbotron">
        <div class="container">
            <h1>Want to Learn About Sharks?</h1>
            <p>Are you ready to learn about sharks?</p>
            <br>
            <p><a class="btn btn-primary btn-lg" href="/sharks" role="button">Get Shark Info</a>
            </p>
        </div>
    </div>
    <div class="container">
        <div class="row">
            <div class="col-lg-6">
                <h3>Not all sharks are alike</h3>
                <p>Though some are dangerous, sharks generally do not attack humans. Out of the 500 species known to researchers, only 30 have been known to attack humans.
                </p>
            </div>
            <div class="col-lg-6">
                <h3>Sharks are ancient</h3>
                <p>There is evidence to suggest that sharks lived up to 400 million years ago.
                </p>
            </div>
        </div>
    </div>
</body>

</html>

顶级导航栏允许用户在HomeSharks页面之间切换。在navbar-nav子组件中,我们使用了Bootstrap的active类来向用户指示当前页面。我们还指定了指向我们的静态页面的路由,这些路由与我们在app.js中定义的路由相匹配:

...
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
   <ul class="nav navbar-nav mr-auto">
      <li class="active nav-item"><a href="/" class="nav-link">Home</a>
      </li>
      <li class="nav-item"><a href="/sharks" class="nav-link">Sharks</a>
      </li>
   </ul>
</div>
...

此外,我们在jumbotron的按钮中创建了一个链接到我们的鲨鱼信息页面:

~/node_project/views/index.html

...
<div class="jumbotron">
   <div class="container">
      <h1>Want to Learn About Sharks?</h1>
      <p>Are you ready to learn about sharks?</p>
      <br>
      <p><a class="btn btn-primary btn-lg" href="/sharks" role="button">Get Shark Info</a>
      </p>
   </div>
</div>
...

在头部,我们还链接了一个自定义样式表:

~/node_project/views/index.html

...
<link href="css/styles.css" rel="stylesheet">
...

我们将在这一步的最后创建这个样式表。

保存并关闭文件后,我们可以创建我们的鲨鱼信息页面sharks.html,它将为感兴趣的用户提供有关鲨鱼的更多信息。

打开文件:

nano views/sharks.html

添加以下代码,该代码导入Bootstrap和自定义样式表,并为用户提供有关某些鲨鱼的详细信息:

~/node_project/views/sharks.html

<!DOCTYPE html>
<html lang="en">

<head>
    <title>About Sharks</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="<https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css>" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
    <link href="css/styles.css" rel="stylesheet">
    <link href="<https://fonts.googleapis.com/css?family=Merriweather:400,700>" rel="stylesheet" type="text/css">
</head>
<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">Toggle navigation</span>
        </button> <a class="navbar-brand" href="/">Everything Sharks</a>
        <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
            <ul class="nav navbar-nav mr-auto">
                <li class="nav-item"><a href="/" class="nav-link">Home</a>
                </li>
                <li class="active nav-item"><a href="/sharks" class="nav-link">Sharks</a>
                </li>
            </ul>
        </div>
    </div>
</nav>
<div class="jumbotron text-center">
    <h1>Shark Info</h1>
</div>
<div class="container">
    <div class="row">
        <div class="col-lg-6">
            <p>
                <div class="caption">Some sharks are known to be dangerous to humans, though many more are not. The sawshark, for example, is not considered a threat to humans.
                </div>
                <img src="<https://assets..com/articles/docker_node_image/sawshark.jpg>" alt="Sawshark">
            </p>
        </div>
        <div class="col-lg-6">
            <p>
                <div class="caption">Other sharks are known to be friendly and welcoming!</div>
                <img src="<https://assets..com/articles/docker_node_image/sammy.png>" alt="Sammy the Shark">
            </p>
        </div>
    </div>
</div>

</html>

注意,在此文件中,我们再次使用active类来指示当前页面。

保存并关闭文件后。

最后,通过在views目录中创建一个css文件夹来创建你在index.htmlsharks.html中链接的自定义CSS样式表:

mkdir views/css

打开样式表:

nano views/css/styles.css

添加以下代码,它将为我们的页面设置所需的颜色和字体:

~/node_project/views/css/styles.css

.navbar {
    margin-bottom: 0;
}

body {
    background: #020A1B;
    color: #ffffff;
    font-family: 'Merriweather', sans-serif;
}

h1,
h2 {
    font-weight: bold;
}

p {
    font-size: 16px;
    color: #ffffff;
}

.jumbotron {
    background: #0048CD;
    color: white;
    text-align: center;
}

.jumbotron p {
    color: white;
    font-size: 26px;
}

.btn-primary {
    color: #fff;
    text-color: #000000;
    border-color: white;
    margin-bottom: 5px;
}

img,
video,
audio {
    margin-top: 20px;
    max-width: 80%;
}

div.caption: {
    float: left;
    clear: both;
}

除了设置字体和颜色外,此文件还通过指定max-width为80%来限制图像的大小。这将防止它们在页面上占用过多的空间。

保存并关闭文件后。

随着应用程序文件的到位和项目依赖项的安装,你已经准备好启动应用程序了。

如果你按照前提条件中的初始服务器设置教程进行了操作,你将有一个只允许SSH流量的活跃防火墙。要允许流量通过端口8080,请运行:

sudo ufw allow 8080

要启动应用程序,请确保你在项目的根目录中:

cd ~/node_project

使用node app.js启动应用程序:

node app.js

导航到http://your_server_ip:8080。你将加载以下登陆页面:

点击获取鲨鱼信息按钮。以下信息页面将加载:

现在你已经有一个运行中的应用程序。当你准备好时,通过输入CTRL+C退出服务器。我们现在可以继续创建Dockerfile,它将允许我们重现和扩展这个应用程序。

第3步 — 编写Dockerfile

你的Dockerfile指定了在执行应用程序容器时将包含什么。使用Dockerfile允许你定义容器环境,并避免依赖项或运行时版本的差异。

遵循这些关于构建优化容器的指南,我们将使我们的镜像尽可能高效,通过最小化镜像层的数量和限制镜像的功能到一个单一目的——重现我们的应用程序文件和静态内容。

在项目的根目录中创建Dockerfile:

nano Dockerfile

Docker镜像是使用一系列相互建立的层来创建的。我们的第一步将是添加应用程序的基础镜像,这将形成应用程序构建的起点。

让我们使用node:10-alpine镜像,因为在撰写本文时这是推荐的Node.js LTS版本。alpine镜像源自Alpine Linux项目,将帮助我们保持镜像大小的精简。有关是否alpine镜像适合你的项目的更多信息,请查看

Docker Hub Node镜像页面的镜像变体部分的完整讨论。

添加以下FROM指令以设置应用程序的基础镜像:

~/node_project/Dockerfile

FROM node:10-alpine

这个镜像包括Node.js和npm。每个Dockerfile必须以FROM指令开始。

默认情况下,Docker Node镜像包括一个非root的node用户,你可以使用它来避免以root身份运行你的应用程序容器。避免以root身份运行容器,并限制容器内的能力,只限于运行其进程所需的能力,是一种推荐的安全实践。因此,我们将使用node用户的主目录作为我们应用程序的工作目录,并在容器内将它们设置为我们的用户。有关使用Docker Node镜像时的最佳实践的更多信息,请查看这个最佳实践指南。

为了微调容器中应用程序代码的权限,让我们在/home/node中创建node_modules子目录和app目录。创建这些目录将确保它们具有我们想要的权限,这在我们使用npm install在容器中创建本地node模块时将非常重要。除了创建这些目录外,我们还将它们设置为node用户所有:

~/node_project/Dockerfile

...
RUN mkdir -p /home/node/app/node_modules && chown -R node:node /home/node/app

有关合并RUN指令的效用的更多信息,请阅读这个关于管理容器层的讨论。

接下来,将应用程序的工作目录设置为/home/node/app

~/node_project/Dockerfile

...
WORKDIR /home/node/app

如果没有设置WORKDIR,Docker将默认创建一个,因此明确设置它是一个好的做法。

接下来,复制package.jsonpackage-lock.json(对于npm 5+)文件:

~/node_project/Dockerfile

...
COPY package*.json ./

在运行npm install或复制应用程序代码之前添加此COPY指令,允许我们利用Docker的缓存机制。在构建的每个阶段,Docker将检查它是否有特定指令的缓存层。如果我们更改了package.json,这个层将被重建,但如果我们没有,这个指令将允许Docker使用现有的镜像层并跳过重新安装我们的node模块。

为确保所有应用程序文件都由非root的node用户拥有,包括node_modules目录中的内容,在运行npm install之前切换用户到node

~/node_project/Dockerfile

...
USER node

复制项目依赖项并切换用户后,我们可以运行npm install

~/node_project/Dockerfile

...
RUN npm install

接下来,以适当的权限将你的应用程序代码复制到容器中的应用程序目录:

~/node_project/Dockerfile

...
COPY --chown=node:node . .

这将确保应用程序文件由非root的node用户拥有。

最后,暴露容器上的端口8080并启动应用程序:

~/node_project/Dockerfile

...
EXPOSE 8080
CMD [ "node", "app.js" ]

EXPOSE不发布端口,而是作为一种记录容器上将在运行时发布的端口的方式。CMD运行启动应用程序的命令——在这种情况下,是node app.js。请注意,每个Dockerfile中只能有一个CMD指令。如果你包括了多个,只有最后一个会生效。

你可以用Dockerfile做很多事情。有关指令的完整列表,请参考Docker的Dockerfile参考文档。

完整的Dockerfile如下所示:

~/node_project/Dockerfile

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 8080

CMD [ "node", "app.js" ]

编辑完成后保存并关闭文件。

在构建应用程序镜像之前,让我们添加一个.dockerignore文件。与.gitignore文件类似,.dockerignore指定了哪些文件和目录在你的项目目录中不应该被复制到你的容器中。

打开.dockerignore文件:

nano .dockerignore

在文件中,添加你的本地node模块、npm日志、Dockerfile和.dockerignore文件:

~/node_project/.dockerignore

node_modules
npm-debug.log
Dockerfile
.dockerignore

如果你使用Git进行工作,你还会想要添加你的.git目录和.gitignore文件。

保存并关闭文件后。

你现在可以使用docker build命令构建应用程序镜像。使用-t标志与docker build将允许你用一个容易记住的名字标记镜像。因为我们将把镜像推送到Docker Hub,让我们在标签中包含我们的Docker Hub用户名。我们将把镜像标记为nodejs-image-demo,但你可以替换为你自己的选择。记得也把你的Docker Hub用户名替换为your_dockerhub_username

sudo docker build -t your_dockerhub_username/nodejs-image-demo .

.指定构建上下文是当前目录。

构建镜像将需要一两分钟。完成后,检查你的镜像:

sudo docker images

你将收到以下输出:

REPOSITORY                                         TAG                 IMAGE ID            CREATED             SIZE
your_dockerhub_username/nodejs-image-demo          latest              1c723fb2ef12        8 seconds ago       73MB
node                                               10-alpine           f09e7c96b6de        3 weeks ago         70.7MB

现在你可以使用docker run命令创建这个镜像的容器。我们将在这个命令中包含三个标志:

  • p: 这个标志发布容器上的端口并将其映射到主机上的端口。我们将在主机上使用端口80,但如果你需要,可以根据需要修改这个端口。有关这是如何工作的更多信息,请查看Docker文档中关于端口绑定的讨论。
  • d: 这个标志在后台运行容器。
  • -name: 这个标志允许我们给容器一个容易记住的名字。

运行以下命令构建容器:

sudo docker run --name nodejs-image-demo -p 80:8080 -d your_dockerhub_username/nodejs-image-demo

使用docker ps检查你的运行中的容器列表:

sudo docker ps

你将收到以下输出:

CONTAINER ID        IMAGE                                                   COMMAND             CREATED             STATUS              PORTS                  NAMES
e50ad27074a7        your_dockerhub_username/nodejs-image-demo               "node app.js"       8 seconds ago       Up 7 seconds        0.0.0.0:80->8080/tcp   nodejs-image-demo

随着你的容器运行,你现在可以通过导航到你的服务器IP(无需端口)来访问你的应用程序:

http://your_server_ip

你的应用程序登陆页面将再次加载。

现在你已经为你的应用程序创建了一个镜像,你可以将其推送到Docker Hub以备将来使用。

第4步 — 使用仓库与镜像一起工作

通过将你的应用程序镜像推送到像Docker Hub这样的注册表,你使其可用于随后的构建和扩展容器。我们将通过将应用程序镜像推送到仓库,然后使用仓库中的镜像重现我们的容器来演示这个过程。

推送镜像的第一步是登录你在前提条件中创建的Docker Hub账户:

sudo docker login -u your_dockerhub_username

当提示时,输入你的Docker Hub账户密码。以这种方式登录将在用户主目录中创建一个~/.docker/config.json文件,其中包含你的Docker Hub凭据。

你现在可以推送应用程序镜像到Docker Hub,使用你之前创建的标签,your_dockerhub_username/nodejs-image-demo

sudo docker push your_dockerhub_username/nodejs-image-demo

让我们测试镜像注册表的实用性,通过销毁当前的应用程序容器和镜像,并使用仓库中的镜像重建它们。

首先,列出你的运行中的容器:

sudo docker ps

你将得到以下输出:

CONTAINER ID        IMAGE                                       COMMAND             CREATED             STATUS              PORTS                  NAMES
e50ad27074a7        your_dockerhub_username/nodejs-image-demo   "node app.js"       3 minutes ago       Up 3 minutes        0.0.0.0:80->8080/tcp   nodejs-image-demo

使用你的输出中列出的CONTAINER ID,停止运行中的应用程序容器。确保将下面的高亮ID替换为你自己的CONTAINER ID

sudo docker stop e50ad27074a7

列出你所有的容器,使用-a标志:

docker images -a

你将收到以下输出,其中包含你的镜像名称your_dockerhub_username/nodejs-image-demo,以及node镜像和其他构建过程中的镜像:

REPOSITORY                                           TAG                 IMAGE ID            CREATED             SIZE
your_dockerhub_username/nodejs-image-demo            latest              1c723fb2ef12        7 minutes ago       73MB
<none>
2.9MB
           <none>              2e3267d9ac02        4 minutes ago       7
<none>                                               <none>              8352b41730b9        4 minutes ago       73MB
<none>                                               <none>              5d58b92823cb        4 minutes ago       73MB
<none>                                               <none>              3f1e35d7062a        4 minutes ago       73MB
<none>                                               <none>              02176311e4d0        4 minutes ago       73MB
<none>                                               <none>              8e84b33edcda        4 minutes ago       70.7MB
<none>                                               <none>              6a5ed70f86f2        4 minutes ago       70.7MB
<none>                                               <none>              776b2637d3c1        4 minutes ago       70.7MB
node                                                 10-alpine           f09e7c96b6de        3 weeks ago         70.7MB

使用以下命令删除已停止的容器和所有镜像,包括未使用或悬挂的镜像:

docker system prune -a

在输出中提示时输入y确认你想要删除已停止的容器和镜像。请注意,这也会删除你的构建缓存。

你现在已删除了运行你的应用程序镜像的容器和镜像本身。有关删除Docker容器、镜像和卷的更多信息,请查看如何删除Docker镜像、容器和卷。

删除了所有镜像和容器后,你现在可以从Docker Hub拉取应用程序镜像:

docker pull your_dockerhub_username/nodejs-image-demo

再次列出你的镜像:

docker images

你的输出将包含你的应用程序镜像:

REPOSITORY                                     TAG                 IMAGE ID            CREATED             SIZE
your_dockerhub_username/nodejs-image-demo      latest              1c723fb2ef12        11 minutes ago      73MB

你现在可以再次使用第3步中的命令重建你的容器:

docker run --name nodejs-image-demo -p 80:8080 -d your_dockerhub_username/nodejs-image-demo

列出你的运行中的容器:

docker ps
CONTAINER ID        IMAGE                                                   COMMAND             CREATED             STATUS              PORTS                  NAMES
f6bc2f50dff6        your_dockerhub_username/nodejs-image-demo               "node app.js"       4 seconds ago       Up 3 seconds        0.0.0.0:80->8080/tcp   nodejs-image-demo

再次访问http://your_server_ip来查看你的运行中的应用程序。

结论

在本教程中,你创建了一个使用Express和Bootstrap的静态Web应用程序,以及这个应用程序的Docker镜像。你使用这个镜像创建了一个容器,并将镜像推送到了Docker Hub。从那里,你能够销毁你的镜像和容器,并使用你的Docker Hub仓库重新创建它们。