为什么需要 Docker?

想象这样一个场景:你在自己的电脑上辛辛苦苦开发了一个 Web 应用,所有功能都跑得完美无缺。当你满怀信心地把它部署到服务器上时,却惊讶地发现——应用无法启动!经过一番排查,你发现服务器上的 Python 版本不对、依赖包没装全、配置文件位置不对……这种“在我电脑上明明是好的”的窘境,相信很多开发者都经历过。

Docker 的出现,就是为了解决这个“环境一致性问题”。它就像是一个神奇的“集装箱”,可以把你的应用和它所需要的运行环境一起打包。无论在哪个服务器上,只要对方有 Docker 这个“起重机”,就能确保你的应用运行得和在你电脑上一模一样。

今天,我们就来学习如何使用 Docker 部署一个 Web 应用。即使你是一个对 Docker 完全陌生的初学者,看完这篇文章后,你也能够独立完成应用的容器化部署。


一、Docker 基本概念:三个核心元素

在开始动手之前,我们先来理解 Docker 的三个核心概念。把 Docker 想象成一个物流系统,这三个概念就很好理解了:

1. 镜像(Image)—— 装箱单

镜像就像是集装箱的“装箱单”或者“蓝图”。它里面包含了运行一个应用所需的一切:代码、运行时环境(比如 Node.js、Python)、系统工具、库文件、配置等。镜像是一个只读的模板,你可以基于它创建多个完全相同的容器。

打个比方:镜像就像是蛋糕的配方,只要按照这个配方,你就能做出味道一模一样的蛋糕。

2. 容器(Container)—— 正在运行的集装箱

容器是镜像的一个运行实例。当你把镜像“运行”起来,它就变成了一个容器。容器是轻量级的、相互隔离的运行环境,每个容器都拥有自己独立的文件系统、网络和进程空间。

延续上面的比喻:容器就是按照配方做出来的那块蛋糕。你可以用同一个配方(镜像)做出很多块蛋糕(容器),每一块蛋糕都是独立存在的。

3. 仓库(Repository)—— 集装箱码头

仓库是存放镜像的地方,类似于代码仓库。最常用的公共仓库是 Docker Hub,你可以把制作好的镜像推送到仓库中,也可以从仓库中拉取别人制作好的镜像。

这就好比是码头上的集装箱堆场,你可以把做好的集装箱(镜像)存放在那里,也可以从那里取用别人放好的集装箱。

这三个概念的关系可以用一句话总结:从仓库拉取镜像,用镜像创建容器,容器运行你的应用


二、准备工作:环境搭建

2.1 安装 Docker

在使用 Docker 之前,需要先在你的电脑上安装它。Docker 支持 Windows、macOS 和 Linux 系统。

Windows 用户

  • 访问 Docker 官网(docs.docker.com)下载 Docker Desktop for Windows

  • 双击安装程序,按照提示完成安装

  • 安装完成后,你可能需要重启电脑

macOS 用户

  • 同样从官网下载 Docker Desktop for Mac

  • 将应用拖入“应用程序”文件夹即可

Linux 用户

  • 使用包管理器安装,以 Ubuntu 为例:

    bash

    复制

    下载
    sudo apt update
    sudo apt install docker.io
    sudo systemctl start docker
    sudo systemctl enable docker

2.2 验证安装

安装完成后,打开终端(命令行),输入以下命令验证 Docker 是否安装成功:

bash

复制

下载

docker --version

如果看到类似 Docker version 24.0.7 的输出,说明安装成功了。


三、编写 Dockerfile:为你的应用“装箱”

Dockerfile 是一个文本文件,里面包含了构建镜像所需要的“配方”。我们来一步步学习如何编写它。

3.1 准备一个示例 Web 应用

首先,让我们创建一个简单的 Node.js Web 应用作为示例。在你的电脑上新建一个文件夹,命名为 my-web-app,然后创建以下两个文件:

app.js —— Web 应用的主文件:

javascript

复制

下载

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

app.get('/', (req, res) => {
  res.send(`
    <html>
      <head>
        <title>我的 Docker 应用</title>
        <style>
          body { font-family: Arial; text-align: center; margin-top: 100px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; }
          h1 { font-size: 48px; }
          p { font-size: 20px; }
        </style>
      </head>
      <body>
        <h1>🎉 欢迎来到 Docker 世界!</h1>
        <p>你的应用正在容器中运行</p>
        <p>容器 ID:${process.env.HOSTNAME || 'unknown'}</p>
      </body>
    </html>
  `);
});

app.listen(port, () => {
  console.log(`应用运行在 http://localhost:${port}`);
});

package.json —— 项目依赖文件:

json

复制

下载

{
  "name": "my-web-app",
  "version": "1.0.0",
  "description": "一个简单的 Docker 示例应用",
  "main": "app.js",
  "scripts": {
    "start": "node app.js"
  },
  "dependencies": {
    "express": "^4.18.2"
  }
}

3.2 编写 Dockerfile

在 my-web-app 文件夹中,创建一个名为 Dockerfile(注意没有文件后缀)的文件,内容如下:

dockerfile

复制

下载

# 第1步:选择基础镜像
# 就像一个蛋糕配方,我们选择别人已经做好的“底胚”
FROM node:18-alpine

# 第2步:设置工作目录
# 在容器内部创建并进入 /app 目录
WORKDIR /app

# 第3步:复制依赖文件
# 先把 package.json 复制进去,利用 Docker 的缓存机制
COPY package*.json ./

# 第4步:安装依赖
# 运行 npm install,安装应用所需的所有依赖
RUN npm install

# 第5步:复制应用代码
# 将当前目录下的所有文件复制到容器的工作目录
COPY . .

# 第6步:暴露端口
# 告诉 Docker 这个容器会使用 3000 端口
EXPOSE 3000

# 第7步:启动命令
# 容器启动时要运行的命令
CMD ["npm", "start"]

3.3 逐行解析 Dockerfile

让我们逐行理解这个 Dockerfile:

  • FROM node:18-alpine:这就像是在说“我要基于 Node.js 18 版本的 Alpine Linux 镜像来构建”。Alpine 是一个非常轻量的 Linux 发行版,可以让镜像体积很小。

  • WORKDIR /app:在容器内创建 /app 目录,并把它设为当前工作目录。后续的命令都会在这个目录下执行。

  • COPY package*.json ./:把本地的 package.json 和 package-lock.json 复制到容器的当前目录。之所以先复制依赖文件再复制代码,是为了利用 Docker 的缓存机制——只要依赖文件没变,下次构建时就不会重复执行 npm install,大大加快构建速度。

  • RUN npm install:在容器内执行 npm install 命令,安装所有依赖。

  • COPY . .:将当前目录的所有文件复制到容器内。注意,我们可以在项目根目录创建 .dockerignore 文件,像 .gitignore 一样忽略不需要的文件(比如 node_modules)。

  • EXPOSE 3000:这只是文档性的声明,告诉使用这个镜像的人,容器会监听 3000 端口。

  • CMD ["npm", "start"]:容器启动时执行的命令。这里我们运行 npm start 来启动应用。


四、构建与运行:让容器跑起来

4.1 构建镜像

打开终端,进入 my-web-app 目录,执行以下命令构建镜像:

bash

复制

下载

docker build -t my-web-app .

命令解析:

  • docker build:构建镜像的命令

  • -t my-web-app:给镜像起一个名字(tag),方便后续使用

  • .:指定构建上下文为当前目录

构建过程会逐行执行 Dockerfile 中的指令,你会在终端看到类似这样的输出:

text

复制

下载

[+] Building 15.2s (10/10) FINISHED
 => [1/5] FROM node:18-alpine
 => [2/5] WORKDIR /app
 => [3/5] COPY package*.json ./
 => [4/5] RUN npm install
 => [5/5] COPY . .
 => exporting to image
 => => naming to docker.io/library/my-web-app

构建完成后,用以下命令查看已创建的镜像:

bash

复制

下载

docker images

你会看到 my-web-app 出现在列表中。

4.2 运行容器

镜像构建完成后,就可以运行容器了:

bash

复制

下载

docker run -d -p 8080:3000 --name my-app my-web-app

命令解析:

  • docker run:运行容器的命令

  • -d:让容器在后台运行(detached mode)

  • -p 8080:3000:端口映射,将宿主机的 8080 端口映射到容器的 3000 端口。这样我们访问本机的 8080 端口,就能访问到容器内运行的 Web 应用

  • --name my-app:给容器起一个名字

  • my-web-app:指定使用哪个镜像

运行成功后,打开浏览器,访问 http://localhost:8080,你应该能看到刚才编写的 Web 页面!

4.3 常用容器管理命令

学习几个常用的容器管理命令:

bash

复制

下载

# 查看正在运行的容器
docker ps

# 查看所有容器(包括已停止的)
docker ps -a

# 停止容器
docker stop my-app

# 启动已停止的容器
docker start my-app

# 删除容器(需要先停止)
docker rm my-app

# 查看容器日志
docker logs my-app

# 进入容器的命令行
docker exec -it my-app sh

五、优化 Dockerfile:让镜像更“轻”

上面的 Dockerfile 已经能工作,但还有一些优化空间。让我们来改进一下:

dockerfile

复制

下载

# 使用更具体的基础镜像版本
FROM node:18-alpine

# 设置时区(可选)
RUN apk add --no-cache tzdata
ENV TZ=Asia/Shanghai

WORKDIR /app

# 只复制 package.json 和 package-lock.json
COPY package*.json ./

# 只安装生产环境依赖,减少镜像体积
RUN npm ci --only=production

# 复制应用代码
COPY . .

# 使用非 root 用户运行,提高安全性
RUN addgroup -g 1001 -S nodejs && \
    adduser -S nodejs -u 1001 -G nodejs
USER nodejs

EXPOSE 3000

# 使用 exec 形式的 CMD
CMD ["node", "app.js"]

优化点说明:

  • 使用 npm ci 替代 npm install,安装更快更可靠

  • --only=production 只安装生产环境依赖,排除开发依赖

  • 创建非 root 用户运行应用,提高安全性

  • 使用 node app.js 直接运行,而不是通过 npm 脚本


六、推送到仓库:让应用“上云”

构建好的镜像只存在于本地,如果想在其他服务器上使用,需要把它推送到镜像仓库。

6.1 注册 Docker Hub 账号

  1. 访问 hub.docker.com

  2. 点击 Sign Up 注册一个账号

  3. 记住你的用户名

6.2 登录并推送镜像

bash

复制

下载

# 登录 Docker Hub
docker login
# 输入你的用户名和密码

# 给镜像打上标签(tag)
# 格式:docker tag 本地镜像 用户名/仓库名:版本号
docker tag my-web-app your-username/my-web-app:v1.0

# 推送到仓库
docker push your-username/my-web-app:v1.0

推送成功后,任何人都可以通过 docker pull your-username/my-web-app:v1.0 来拉取你的镜像并运行了。


七、部署到云平台:从本地到云端

当你的应用运行稳定后,就可以把它部署到云平台上了。这里介绍几种常见的部署方式。

7.1 部署到云服务器

如果你有一台云服务器(比如阿里云 ECS、腾讯云 CVM、AWS EC2 等),部署非常简单:

第一步:连接到云服务器(使用 SSH)

bash

复制

下载

ssh root@你的服务器IP

第二步:在服务器上安装 Docker(参考前面的安装步骤)

第三步:拉取镜像并运行

bash

复制

下载

# 拉取镜像
docker pull your-username/my-web-app:v1.0

# 运行容器
docker run -d -p 80:3000 --name my-web-app your-username/my-web-app:v1.0

注意,这里我们映射到了 80 端口,这样用户直接访问你的服务器 IP 就能看到应用了。

第四步:配置防火墙

确保云服务器的防火墙开放了 80 端口。

7.2 使用 Docker Compose(进阶)

如果你的应用比较复杂,比如需要同时运行 Web 应用和数据库,可以使用 Docker Compose。

创建一个 docker-compose.yml 文件:

yaml

复制

下载

version: '3.8'

services:
  web:
    image: your-username/my-web-app:v1.0
    ports:
      - "80:3000"
    environment:
      - NODE_ENV=production
    restart: always

  # 如果需要数据库,可以这样配置
  # database:
  #   image: mysql:8.0
  #   environment:
  #     MYSQL_ROOT_PASSWORD: your-password
  #   volumes:
  #     - db-data:/var/lib/mysql

# volumes:
#   db-data:

然后在服务器上运行:

bash

复制

下载

docker-compose up -d

7.3 部署到云容器服务

各大云厂商都提供了容器托管服务,可以免去管理服务器的麻烦:

云厂商服务名称特点
阿里云容器服务 ACK与阿里云生态集成好
腾讯云容器服务 TKE腾讯云原生服务
华为云云容器引擎 CCE企业级容器服务
AWSECS / EKS全球领先的容器服务

这些服务的共同特点是:你只需要上传镜像,平台会自动帮你运行和管理容器,自动处理扩缩容、负载均衡等问题。


八、常见问题与解决方案

8.1 端口被占用

问题:运行容器时提示端口已被占用

解决:换个端口映射

bash

复制

下载

# 用 8081 替代 8080
docker run -d -p 8081:3000 my-web-app

8.2 容器启动后立即退出

问题:容器启动后就停止了

解决:查看日志找出原因

bash

复制

下载

# 查看容器日志
docker logs 容器ID

# 交互式运行,方便调试
docker run -it my-web-app sh

常见原因:端口配置错误、依赖未安装、环境变量缺失等。

8.3 镜像体积过大

问题:构建的镜像有好几百 MB

解决

  • 使用 Alpine 版本的基础镜像(如 node:18-alpine

  • 使用多阶段构建

  • 清理不必要的文件

8.4 文件权限问题

问题:容器内无法写入文件

解决:创建非 root 用户并赋予权限,或者使用卷挂载

bash

复制

下载

# 挂载宿主机目录到容器
docker run -v /host/data:/app/data my-web-app

结语

通过这篇文章,我们学习了 Docker 的核心概念,亲手编写了 Dockerfile,构建并运行了容器,最后还将应用部署到了云平台。现在,你已经掌握了将 Web 应用容器化的完整流程。

Docker 的世界远比这篇文章介绍的更加广阔。当你熟悉了基础操作后,可以继续探索以下方向:

  • Docker Compose:管理多容器应用

  • Docker Swarm 或 Kubernetes:容器编排

  • CI/CD 集成:自动构建和部署

  • 容器安全最佳实践

容器化已经成为现代软件交付的标准方式。掌握 Docker,不仅能让你的部署工作变得轻松,更能让你在技术道路上走得更远。

下一步建议:现在就动手试试吧!打开终端,创建一个 Node.js 应用,按照文章步骤完成你的第一次 Docker 部署。遇到问题不要怕,看看日志,查查资料,解决掉它,你就真正掌握这项技能了。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐