From Code on Your Laptop to a Universal Box: A Beginner's Guide to Dockerizing Node.js

From Code on Your Laptop to a Universal Box: A Beginner’s Guide to Dockerizing Node.js

从笔记本代码到通用容器:Node.js Docker 化入门指南

As a software engineer, one of the first frustrating phrases you will hear is, “Well, it works on my machine!” This happens when code runs perfectly on your computer but fails on a colleague’s laptop or a production server. The reason is usually a small difference in the environment, like a different Node.js version or a missing system library. 作为一名软件工程师,你最早听到的令人沮丧的话之一就是:“在我的机器上运行没问题啊!”这种情况通常发生在代码在你的电脑上运行完美,但在同事的笔记本或生产服务器上却报错时。原因通常是环境中的细微差异,比如 Node.js 版本不同或缺少某个系统库。

This is where Docker comes in. Think of Docker as a way to create a standard, universal box for your application. This box contains everything your code needs to run: the code itself, libraries, tools, and settings. You build this box once, and then you can ship it and run it anywhere, and it will always work the same way. In this guide, we will take a simple Node.js web server and package it into one of these universal boxes using Docker. 这就是 Docker 的用武之地。你可以把 Docker 看作是为你的应用程序创建一个标准化的“通用盒子”。这个盒子里包含了代码运行所需的一切:代码本身、库、工具和配置。你只需构建一次这个盒子,就可以将其分发到任何地方运行,且效果始终如一。在本指南中,我们将使用 Docker 将一个简单的 Node.js Web 服务器打包进这样一个通用盒子中。

What You Will Need

准备工作

Before we start, make sure you have these two things installed on your computer: 在开始之前,请确保你的电脑上安装了以下两样东西:

  • Node.js: To run our simple application locally first.
  • Node.js:用于先在本地运行我们的简单应用。
  • Docker Desktop: The application that lets you build and run Docker containers.
  • Docker Desktop:用于构建和运行 Docker 容器的应用程序。

That’s it. Let’s get started. 准备就绪,让我们开始吧。

Step 1: Create a Simple Node.js App

第一步:创建一个简单的 Node.js 应用

First, we need an application to package. Let’s create a very basic web server using Express, a popular Node.js framework. Create a new folder for your project. Inside that folder, create two files: package.json and index.js. 首先,我们需要一个可以打包的应用程序。让我们使用流行的 Node.js 框架 Express 创建一个非常基础的 Web 服务器。创建一个新的项目文件夹,并在其中创建两个文件:package.jsonindex.js

package.json

This file tells Node.js about our project and its dependencies. The only dependency we need is express. 该文件向 Node.js 说明我们的项目及其依赖项。我们唯一需要的依赖是 express。

{
  "name": "simple-node-app",
  "version": "1.0.0",
  "description": "A simple Node.js app for Docker",
  "main": "index.js",
  "scripts": {
    "start": "node index.js"
  },
  "dependencies": {
    "express": "^4.18.2"
  }
}

index.js

This is our actual server code. It creates a web server that listens for requests and sends back a simple message. 这是我们实际的服务器代码。它创建了一个 Web 服务器,用于监听请求并返回一条简单的消息。

const express = require('express');
const app = express();
const PORT = 3000;

app.get('/', (req, res) => {
  res.send('Hello from my Node.js app!');
});

app.listen(PORT, () => {
  console.log(`Server is running on http://localhost:${PORT}`);
});

Now, open your terminal in the project folder and run these commands: 现在,在项目文件夹中打开终端并运行以下命令:

  1. Install the dependency: npm install

  2. Start the server: node index.js

  3. 安装依赖:npm install

  4. 启动服务器:node index.js

If you open your web browser and go to http://localhost:3000, you should see the message “Hello from my Node.js app!”. Great! Our app works locally. Now let’s put it in a box. 如果你打开浏览器访问 http://localhost:3000,你应该能看到 “Hello from my Node.js app!” 的消息。太棒了!我们的应用在本地运行正常。现在让我们把它装进盒子里。

Step 2: Understanding Docker Concepts

第二步:理解 Docker 概念

Before we write the instructions for our box, let’s quickly learn three key Docker terms. 在编写盒子说明书之前,让我们快速了解三个关键的 Docker 术语。

  • Dockerfile: This is a simple text file with a list of instructions. It’s like a recipe for building our box. We will write this file ourselves.
  • Dockerfile:这是一个包含一系列指令的简单文本文件。它就像是构建盒子的“食谱”。我们将亲自编写这个文件。
  • Image: When you follow the recipe in the Dockerfile, you create an Image. An image is a blueprint. It’s a saved, unchangeable package that contains our application and all its needs.
  • Image(镜像):当你按照 Dockerfile 中的食谱操作时,就会创建一个镜像。镜像是一个蓝图,是一个已保存且不可更改的包,包含了我们的应用程序及其所有需求。
  • Container: A container is a running instance of an image. If the image is the blueprint, the container is the actual house built from that blueprint. You can create many containers from a single image.
  • Container(容器):容器是镜像的运行实例。如果镜像是一张蓝图,那么容器就是根据蓝图建造出来的实际房屋。你可以从同一个镜像创建多个容器。

The flow is simple: you write a Dockerfile, use it to build an Image, and then run that Image as a Container. 流程很简单:编写 Dockerfile,用它构建镜像,然后将该镜像作为容器运行。

Step 3: Writing Your First Dockerfile

第三步:编写你的第一个 Dockerfile

In the same project folder, create a new file named Dockerfile (no extension, just that name). This file will contain the step-by-step instructions for Docker. 在同一个项目文件夹中,创建一个名为 Dockerfile 的新文件(没有扩展名,就叫这个名字)。该文件将包含 Docker 的分步指令。

# Start from an official Node.js image.
# The 'alpine' version is very small, which is great.
FROM node:18-alpine

# Create and set the working directory inside the container.
WORKDIR /app

# Copy package.json and package-lock.json first.
# This helps Docker use its cache smartly.
COPY package*.json ./

# Install the application dependencies inside the container.
RUN npm install

# Now, copy the rest of your application's source code.
COPY . .

# Tell Docker that the container listens on port 3000.
EXPOSE 3000

# The command to run when the container starts.
CMD ["node", "index.js"]

Let’s break this down line by line: 让我们逐行解析:

  • FROM node:18-alpine: Every Docker image starts from a base image. Here, we start with an official image that already has Node.js version 18 installed on a minimal version of Linux called Alpine.
  • FROM node:18-alpine:每个 Docker 镜像都从一个基础镜像开始。这里我们使用了一个官方镜像,它在名为 Alpine 的精简版 Linux 上预装了 Node.js 18。
  • WORKDIR /app: This sets the default location inside the container for all subsequent commands. It’s like running cd /app.
  • WORKDIR /app:这为后续所有命令设置了容器内的默认位置。就像运行 cd /app 一样。
  • *COPY package.json ./**: We copy our package files into the /app directory. We do this before copying our code. This is a smart trick. Docker builds in layers. If our code changes but package.json does not, Docker can reuse the npm install layer from a previous build, which saves a lot of time.
  • *COPY package.json ./**:我们将包文件复制到 /app 目录。我们在复制代码之前这样做是一个巧妙的技巧。Docker 是分层构建的。如果代码变了但 package.json 没变,Docker 就可以复用之前构建的 npm install 层,从而节省大量时间。
  • RUN npm install: This runs the command to install our dependencies inside the container.
  • RUN npm install:这会在容器内运行安装依赖的命令。
  • COPY . .: Now we copy the rest of our files (like index.js) into the container.
  • COPY . .:现在我们将剩余的文件(如 index.js)复制到容器中。
  • EXPOSE 3000: This is like a piece of documentation. It tells Docker that our application inside the container will be using port 3000. It doesn’t actually open the port to the outside world.
  • EXPOSE 3000:这就像是一份文档说明。它告诉 Docker 容器内的应用程序将使用 3000 端口。它并不会真正向外部世界开放该端口。
  • CMD [“node”, “index.js”]: This is the final command that will be executed when the container starts. It runs our app.
  • CMD [“node”, “index.js”]:这是容器启动时执行的最终命令,用于运行我们的应用。

Step 4: Build the Image and Run the Container

第四步:构建镜像并运行容器

Now for the magic part. Go back to your terminal, make sure you are in your project directory, and run this command: 现在是见证奇迹的时刻。回到终端,确保你在项目目录下,并运行以下命令:

# The -t flag lets you 'tag' or name your image.
# The '.' at the end tells Docker to look for the Dockerfile in the current directory.
docker build -t my-node-app .

Docker will now execute the steps in your Dockerfile. You will see it downloading the base image and running your commands. Once it’s finished, you have a Docker image named my-node-app. Docker 现在将执行 Dockerfile 中的步骤。你会看到它正在下载基础镜像并运行你的命令。完成后,你就拥有了一个名为 my-node-app 的 Docker 镜像。

Now, let’s run it as a container: 现在,让我们将其作为容器运行:

docker run -p 4000:3000 my-node-app

Let’s understand this command: 让我们理解一下这个命令:

  • docker run: The command to start a container.
  • docker run:启动容器的命令。
  • -p 4000:3000: This is the port mapping. It connects port 4000 on your computer (the host) to port 3000 inside the container. Remember, EXPOSE 3000 only documented the port. This -p flag actually opens it up.
  • -p 4000:3000:这是端口映射。它将你电脑(宿主机)上的 4000 端口连接到容器内的 3000 端口。记住,EXPOSE 3000 只是记录了端口,而 -p 标志才是真正将其开放。
  • my-node-app: The name of the image we want to run.
  • my-node-app:我们要运行的镜像名称。

Now, open your browser and go to http://localhost:4000. You will see the same message: “Hello from my Node.js app!” 现在,打开浏览器访问 http://localhost:4000。你将看到同样的消息:“Hello from my Node.js app!”