Docker - 配置 Node.js
将 Node.js 应用程序 Docker 化是一种可靠的方式,可以将应用程序及其依赖项打包成一个可控且可重现的单元。这确保了在不同环境中部署的一致性,并简化了从开发到生产的工作流程。
在本章中,我们将带您从设置 Node.js 项目开始,一步步运行在 Docker container 中的应用程序。我们将涵盖重要主题,如组织项目、创建 Dockerfile 和管理 container。到本章结束时,您将完全理解如何将 Node.js 应用程序 Docker 化,更重要的是,能够将这些概念应用到您自己的项目中。
前提条件
在 Docker 化我们的 Node.js 应用程序之前,您应该了解以下几个前提条件 −
- 已安装 Node.js 和 npm(或 yarn) − 这些对于开发 Node.js 应用程序是必不可少的。
- 对 Node.js 和 Express.js 的基本了解 − 熟悉这些框架将很有帮助。
- 代码编辑器或 IDE − 用于编写和管理您的代码。
- 版本控制系统(可选) − 用于管理项目代码(例如 Git)。
创建仓库
为了组织您的项目,请创建一个新的 Git 仓库 −
选择仓库名称 − 为您的项目选择一个描述性的名称,例如 node-blog。
初始化 Git 仓库
git init
创建远程仓库(可选) − 如果您想协作或备份代码,请在 GitHub、GitLab 或 Bitbucket 等平台上创建远程仓库。
目录结构
我们将创建一个简单的博客应用程序。请按照以下方式为您的项目创建目录结构 −
node-blog/
package.json
index.js
routes/
posts.js
users.js
models/
posts.js
users.js
controllers/
postsController.js
usersController.js
public/
index.html
Dockerfile
.gitignore
- package.json − 存储项目依赖项和元数据。
- index.js − 应用程序的主要入口点。
- routes − 包含不同 API 端点的路由定义。
- models − 定义应用程序的数据模型。
- controllers − 处理业务逻辑并与模型交互。
- public − 存储静态文件,如 html、css 等。
- Dockerfile − 定义 Docker image 的构建指令。
- .gitignore − 指定要从 Git 版本控制中排除的文件和目录。
这种结构将为您的博客应用程序提供坚实的基础。您可以根据项目的具体要求进行调整。
让我们继续下一个部分:添加路由。
添加路由
让我们创建一个基本路由来处理博客首页的 GET 请求。
步骤 1. 安装 Express.js
$ npm install express
步骤 2. 安装 Bootstrap
$ npm install bootstrap
步骤 3. 创建 index.js 文件
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.send('Hello from your blog!');
});
app.listen(3000, () => {
console.log('Server listening on port 3000');
});
步骤 4. 运行应用程序
node index.js
说明
- 首先,我们导入 Express 库并创建一个 Express 应用程序实例。
- 然后,我们使用 app.get() 方法定义了一个路由来处理根路径 ('/') 的 GET 请求。
- 路由处理器发送一个简单的 'Hello from your blog!' 消息作为响应。
- 最后,我们在 3000 端口启动服务器。

完成代码
在深入代码之前,让我们先建立项目结构 −
project-directory/
models/
posts.js
users.js
controllers/
postsController.js
usersController.js
routes/
posts.js
users.js
public/
index.html
index.js
models/posts.js
let posts = [];
let nextId = 1;
const createPost = (title, content, authorId) => {
const post = { id: nextId++, title, content, authorId };
posts.push(post);
return post;
};
const getPosts = () => {
return posts;
};
const getPostById = (id) => {
return posts.find(post => post.id === parseInt(id));
};
const updatePost = (id, title, content) => {
const postIndex = posts.findIndex(post => post.id === parseInt(id));
if (postIndex !== -1) {
posts[postIndex] = { ...posts[postIndex], title, content };
return posts[postIndex];
}
return null;
};
const deletePost = (id) => {
const postIndex = posts.findIndex(post => post.id === parseInt(id));
if (postIndex !== -1) {
return posts.splice(postIndex, 1)[0];
}
return null;
};
module.exports = {
createPost,
getPosts,
getPostById,
updatePost,
deletePost,
};
models/users.js
let users = [];
let nextUserId = 1;
const createUser = (username, email, password) => {
const user = { id: nextUserId++, username, email, password };
users.push(user);
return user;
};
const getUserByUsername = (username) => {
return users.find(user => user.username === username);
};
module.exports = {
createUser,
getUserByUsername,
};
controllers/postsController.js
const postsModel = require('../models/posts');
const getPosts = (req, res) => {
const posts = postsModel.getPosts();
res.json(posts);
};
const createPost = (req, res) => {
const { title, content, authorId } = req.body;
const post = postsModel.createPost(title, content, authorId);
res.status(201).json(post);
};
const getPostById = (req, res) => {
const { id } = req.params;
const post = postsModel.getPostById(id);
if (post) {
res.json(post);
} else {
res.status(404).json({ message: 'Post not found' });
}
};
const updatePost = (req, res) => {
const { id } = req.params;
const { title, content } = req.body;
const updatedPost = postsModel.updatePost(id, title, content);
if (updatedPost) {
res.json(updatedPost);
} else {
res.status(404).json({ message: 'Post not found' });
}
};
const deletePost = (req, res) => {
const { id } = req.params;
const deletedPost = postsModel.deletePost(id);
if (deletedPost) {
res.json({ message: 'Post deleted' });
} else {
res.status(404).json({ message: 'Post not found' });
}
};
module.exports = {
getPosts,
createPost,
getPostById,
updatePost,
deletePost,
};
controllers/usersController.js
const usersModel = require('../models/users');
const createUser = (req, res) => {
const { username, email, password } = req.body;
const user = usersModel.createUser(username, email, password);
res.status(201).json(user);
};
const getUserByUsername = (req, res) => {
const { username } = req.params;
const user = usersModel.getUserByUsername(username);
if (user) {
res.json(user);
} else {
res.status(404).json({ message: 'User not found' });
}
};
module.exports = {
createUser,
getUserByUsername,
};
routes/posts.js
const express = require('express');
const router = express.Router();
const postsController = require('../controllers/postsController');
router.get('/', postsController.getPosts);
router.post('/', postsController.createPost);
router.get('/:id', postsController.getPostById);
router.put('/:id', postsController.updatePost);
router.delete('/:id', postsController.deletePost);
module.exports = router;
routes/users.js
const express = require('express');
const router = express.Router();
const usersController = require('../controllers/usersController');
router.post('/', usersController.createUser);
router.get('/:username', usersController.getUserByUsername);
module.exports = router;
index.js
const express = require('express');
const path = require('path');
const posts = require('./routes/posts');
const users = require('./routes/users');
const app = express();
app.use(express.json());
// Import Bootstrap CSS
app.use('/css', express.static(path.join(__dirname, 'node_modules/bootstrap/dist/css')));
app.use(express.static(path.join(__dirname, 'public')));
app.use('/posts', posts);
app.use('/users', users);
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'index.html'));
});
const port = process.env.PORT || 3000;
app.listen(port, () => {
console.log(`Server listening on port ${port}`);
});
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 Blog</title>
<link rel="stylesheet" href="/css/bootstrap.min.css">
</head>
<body>
<div class="container">
<div class="row">
<div class="col-md-6">
<form action="/users" method="POST">
<div class="mb-3">
<label for="username" class="form-label">Username</label>
<input type="text" class="form-control" id="username" name="username" placeholder="Username">
</div>
<div class="mb-3">
<label for="email" class="form-label">Email</label>
<input type="email" class="form-control" id="email" name="email" placeholder="Email">
</div>
<div class="mb-3">
<label for="password" class="form-label">Password</label>
<input type="password" class="form-control" id="password" name="password" placeholder="Password">
</div>
<button type="submit" class="btn btn-primary">Create User</button>
</form>
</div>
<div class="col-md-6">
<form action="/posts" method="POST">
<div class="mb-3">
<label for="title" class="form-label">Title</label>
<input type="text" class="form-control" id="title" name="title" placeholder="Title">
</div>
<div class="mb-3">
<label for="content" class="form-label">Content</label>
<textarea class="form-control" id="content" name="content" rows="3"></textarea>
</div>
<button type="submit" class="btn btn-primary">Create Post</button>
</form>
</div>
</div>
</div>
<script src="/js/bootstrap.bundle.min.js"></script>
</body>
</html>
代码说明
模型
- posts.js − 定义了 posts 的内存存储,包括 CRUD 操作(创建、读取、更新、删除)。
- users.js − 定义了 users 的内存存储,包括用户创建和按用户名检索。
控制器
- postsController.js − 处理与 posts 相关的 HTTP 请求,与 posts 模型交互。
- usersController.js − 处理与 users 相关的 HTTP 请求,与 users 模型交互。
路由
- posts.js − 定义 posts 的 API 端点(GET、POST、PUT、DELETE)。
- users.js − 定义 users 的 API 端点(POST、GET)。
index.js
- 设置 Express 服务器。
- 定义 API 的基础 URL。
- 在指定端口启动服务器。
Index.html
这里我们使用了 Bootstrap 和基本的 HTML 创建了一个表单,允许您创建和查看 users 和 posts。
在本地启动应用程序
- 进入项目目录 − 打开您的终端或命令提示符,并进入项目根目录。
- 安装依赖 − 运行 npm install 以安装 package.json 文件中列出的必需依赖。
- 启动开发服务器 − 执行 node index.js 以启动应用程序。
说明
- npm install 命令将获取并安装应用程序运行所需的所有必要包。
- 运行 node index.js 会执行 JavaScript 文件 index.js,这是应用程序的入口点。这将启动 Node.js 服务器。
测试您的应用程序
一旦服务器运行,打开 Web 浏览器并导航到 http://localhost:3000。您应该看到表明应用程序正在运行的响应。
注意
- 默认端口是 3000,但您可以通过修改 index.js 文件中的端口号来更改它。
- 您可以使用像 nodemon 这样的工具,在代码更改时自动重启。
您现在可以在这里创建 users 和 posts。
将 NodeJs 应用程序 Docker 化
让我们在项目根目录中创建一个名为 Dockerfile 的文件,内容如下
# 使用 Node.js 镜像作为基础镜像 FROM node:18-alpine # 设置工作目录 WORKDIR /app # 复制 package.json 和 package-lock.json 以安装依赖 COPY package*.json ./ # 安装依赖 RUN npm install # 复制应用程序代码的其余部分 COPY . . # 暴露应用程序监听的端口 EXPOSE 3000 # 启动应用程序 CMD ["node", "index.js"]
Dockerfile 说明
- FROM node:18-alpine − 我们将使用 Node.js 18 镜像作为 Docker 镜像的基础镜像。
- WORKDIR /app − 将容器内的工作目录设置为 /app。
- COPY package*.json ./ − 将 package.json 和 package-lock.json 复制到工作目录。
- RUN npm install − 安装项目依赖。
- COPY . . − 将整个项目复制到容器中。
- EXPOSE 3000 − 为应用程序暴露端口 3000。
- CMD ["node", "index.js"] − 指定容器启动时运行的命令。
构建 Docker 镜像
要构建 Docker 镜像,您可以在终端中运行以下命令 −
$ docker build -t my-node-app .
此命令构建 Docker 镜像并将其标记为 my-node-app。
运行 Docker 容器
要运行 Docker 容器,请使用以下命令 −
$ docker run -p 3000:3000 my-node-app
此命令从 my-node-app 镜像创建容器,然后将容器的端口 3000 映射到主机的端口 3000,并启动容器。
现在,您可以通过 http://localhost:3000 访问您的应用程序。

结论
在本章中,我们逐步介绍了开发一个 Node.js 博客应用并使用 Docker 进行容器化的所有步骤。你现在知道如何组织项目、处理用户交互、有效打包应用以供部署,以及创建基础 image 来构建其他 Docker。
虽然本教程非常基础,但如果你计划让这个应用投入生产使用,应该添加功能、安全措施、数据库集成等。Docker 化将让你简化所有开发、测试和部署流程,确保应用在所有环境中的行为一致。
Docker 中设置 Node.js 的常见问题解答
1. 优化 Node.js 应用的 Docker image 的最佳实践是什么?
Docker image 优化的最佳实践是使用最小基础 image 大小并配备精简的 Node.js runtime,仅安装应用所需的依赖,并利用构建缓存。你可以使用生产就绪的 Node.js image,并通过更好的包管理方法进一步优化它。
2. 如何在 Dockerized Node.js 应用中处理环境变量?
你可以在 Dockerfile 中使用 ENV 指令设置环境变量,但最佳实践是在运行时通过 -e 标志传递它。你可以使用环境变量进行配置,以增强灵活性和安全性。
3. 如何调试运行在 Docker container 中的 Node.js 应用?
你可以使用远程调试工具如 Node.js 本身、配备远程调试扩展的 Visual Studio Code 或其他第三方工具,来调试运行在 Docker container 内的 Node.js 应用。在 Dockerfile 中暴露调试端口,并相应配置你的 IDE。