上一篇 下一篇 分享链接 返回 返回顶部

从零搭建一个可 Docker 一键部署的 ChatGPT Web 应用,完整源码实战教程

发布人:慈云数据-客服中心 发布时间:20小时前 阅读量:4

ChatGPT Docker部署教程|附源码

随着大模型应用的普及,越来越多开发者希望将 ChatGPT 能力集成到自己的业务系统、内部工具、知识库问答平台或个人项目中。相比直接在本地运行项目,使用 Docker 部署具有环境一致、迁移方便、启动快速、便于运维等优势。本文将从零开始介绍如何使用 Docker 部署一个简单的 ChatGPT Web 服务,并附带完整源码示例,包括后端接口、前端页面、Dockerfile、docker-compose 配置以及环境变量说明。

本教程适合有一定 Linux、Docker、Node.js 基础的开发者阅读。如果你是初学者,也可以按照步骤逐条执行,基本能够顺利完成部署。


一、项目目标

本文最终实现一个轻量级 ChatGPT Web 应用,具备以下功能:

  1. 提供一个网页聊天界面;
  2. 用户在页面输入问题;
  3. 后端调用 OpenAI Chat Completions API;
  4. 将模型回复返回给前端展示;
  5. 支持通过 Docker 一键部署;
  6. 支持通过环境变量配置 API Key、模型名称、端口等参数。

项目结构如下:

chatgpt-docker-demo/
├── src/
│   ├── server.js
│   └── public/
│       ├── index.html
│       ├── style.css
│       └── app.js
├── package.json
├── Dockerfile
├── docker-compose.yml
├── .env.example
└── README.md

二、部署前准备

在正式开始之前,请确保你的服务器或本地电脑已经安装以下环境。

1. 安装 Docker

如果你使用的是 Ubuntu,可以通过以下命令安装 Docker:

sudo apt update
sudo apt install -y docker.io
sudo systemctl enable docker
sudo systemctl start docker

安装完成后,检查 Docker 版本:

docker --version

如果能够看到类似下面的输出,说明安装成功:

Docker version 24.0.7, build afdd53b

2. 安装 Docker Compose

较新的 Docker 已经内置了 Compose 插件,可以使用以下命令检查:

docker compose version

如果显示版本号,则说明可用。

如果提示命令不存在,可以根据系统环境单独安装 Docker Compose。

3. 准备 OpenAI API Key

你需要准备一个可用的 OpenAI API Key。后续项目会通过环境变量读取该 Key,用于调用 ChatGPT 接口。

注意:

  • 不要把 API Key 写死在源码中;
  • 不要把 .env 文件提交到公开仓库;
  • 如果服务部署在公网,建议增加鉴权或访问限制;
  • 建议为生产环境设置 API 用量限制,避免被滥用。

三、创建项目目录

首先创建项目目录:

mkdir chatgpt-docker-demo
cd chatgpt-docker-demo

创建源码目录:

mkdir -p src/public

四、编写后端源码

本项目后端使用 Node.js + Express 实现。后端主要负责三件事:

  1. 提供静态网页;
  2. 接收前端聊天请求;
  3. 调用 OpenAI API 并返回结果。

创建 src/server.js

const express = require("express");
const path = require("path");
const OpenAI = require("openai");
require("dotenv").config();

const app = express();

const PORT = process.env.PORT || 3000;
const OPENAI_API_KEY = process.env.OPENAI_API_KEY;
const OPENAI_MODEL = process.env.OPENAI_MODEL || "gpt-4o-mini";

if (!OPENAI_API_KEY) {
  console.warn("警告:未检测到 OPENAI_API_KEY,请在环境变量中配置。");
}

const client = new OpenAI({
  apiKey: OPENAI_API_KEY,
});

app.use(express.json({
  limit: "1mb",
}));

app.use(express.static(path.join(__dirname, "public")));

app.get("/api/health", (req, res) => {
  res.json({
    status: "ok",
    message: "ChatGPT Docker Demo is running",
  });
});

app.post("/api/chat", async (req, res) => {
  try {
    const { message, history } = req.body;

    if (!message || typeof message !== "string") {
      return res.status(400).json({
        error: "message 参数不能为空",
      });
    }

    const messages = [
      {
        role: "system",
        content: "你是一个专业、友好、严谨的中文 AI 助手。",
      },
    ];

    if (Array.isArray(history)) {
      for (const item of history) {
        if (
          item &&
          typeof item.role === "string" &&
          typeof item.content === "string" &&
          ["user", "assistant"].includes(item.role)
        ) {
          messages.push({
            role: item.role,
            content: item.content,
          });
        }
      }
    }

    messages.push({
      role: "user",
      content: message,
    });

    const completion = await client.chat.completions.create({
      model: OPENAI_MODEL,
      messages,
      temperature: 0.7,
    });

    const reply = completion.choices?.[0]?.message?.content || "";

    res.json({
      reply,
      model: OPENAI_MODEL,
    });
  } catch (error) {
    console.error("调用 OpenAI API 失败:", error);

    res.status(500).json({
      error: "服务端调用模型失败,请检查 API Key、网络或模型配置。",
      detail: error.message,
    });
  }
});

app.listen(PORT, () => {
  console.log(`ChatGPT Docker Demo 已启动:http://localhost:${PORT}`);
});

这段后端代码非常简洁,但已经具备一个完整 ChatGPT Web 服务的基本能力。它通过 /api/chat 接口接收用户输入,然后将请求转发给 OpenAI API,最后把回复返回给前端。


五、编写前端页面

前端采用原生 HTML、CSS、JavaScript,不依赖 Vue、React 等框架,方便理解和部署。

创建 src/public/index.html




  
  
  ChatGPT Docker Demo
  


  

ChatGPT Docker Demo

一个基于 Docker 部署的 ChatGPT 简易聊天应用

AI
你好,我是你的 AI 助手。请输入你的问题。

创建 src/public/style.css

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Microsoft YaHei", sans-serif;
  background: #f5f7fb;
  color: #222;
}

.page {
  max-width: 960px;
  margin: 0 auto;
  padding: 24px;
}

.header {
  text-align: center;
  margin-bottom: 24px;
}

.header h1 {
  margin: 0;
  font-size: 32px;
}

.header p {
  margin-top: 8px;
  color: #666;
}

.chat-container {
  background: #fff;
  border-radius: 16px;
  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.08);
  overflow: hidden;
}

.chat-box {
  height: 600px;
  overflow-y: auto;
  padding: 24px;
  border-bottom: 1px solid #eee;
}

.message {
  display: flex;
  gap: 12px;
  margin-bottom: 18px;
}

.message.user {
  flex-direction: row-reverse;
}

.avatar {
  width: 40px;
  height: 40px;
  flex-shrink: 0;
  border-radius: 50%;
  background: #10a37f;
  color: #fff;
  display: flex;
  justify-content: center;
  align-items: center;
  font-weight: bold;
}

.message.user .avatar {
  background: #3b82f6;
}

.content {
  max-width: 75%;
  padding: 12px 14px;
  border-radius: 12px;
  line-height: 1.7;
  white-space: pre-wrap;
  background: #f1f5f9;
}

.message.user .content {
  background: #dbeafe;
}

.input-area {
  display: flex;
  gap: 12px;
  padding: 16px;
}

textarea {
  flex: 1;
  height: 80px;
  resize: none;
  border: 1px solid #ddd;
  border-radius: 12px;
  padding: 12px;
  font-size: 16px;
  outline: none;
}

textarea:focus {
  border-color: #10a37f;
}

button {
  width: 100px;
  border: none;
  border-radius: 12px;
  background: #10a37f;
  color: #fff;
  font-size: 16px;
  cursor: pointer;
}

button:disabled {
  background: #aaa;
  cursor: not-allowed;
}

创建 src/public/app.js

const chatBox = document.getElementById("chatBox");
const messageInput = document.getElementById("messageInput");
const sendBtn = document.getElementById("sendBtn");

const history = [];

function appendMessage(role, content) {
  const wrapper = document.createElement("div");
  wrapper.className = `message ${role}`;

  const avatar = document.createElement("div");
  avatar.className = "avatar";
  avatar.textContent = role === "user" ? "我" : "AI";

  const contentDiv = document.createElement("div");
  contentDiv.className = "content";
  contentDiv.textContent = content;

  wrapper.appendChild(avatar);
  wrapper.appendChild(contentDiv);
  chatBox.appendChild(wrapper);

  chatBox.scrollTop = chatBox.scrollHeight;
}

async function sendMessage() {
  const message = messageInput.value.trim();

  if (!message) {
    return;
  }

  appendMessage("user", message);
  history.push({
    role: "user",
    content: message,
  });

  messageInput.value = "";
  sendBtn.disabled = true;
  sendBtn.textContent = "发送中";

  try {
    const response = await fetch("/api/chat", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        message,
        history: history.slice(-10),
      }),
    });

    const data = await response.json();

    if (!response.ok) {
      throw new Error(data.error || "请求失败");
    }

    appendMessage("assistant", data.reply);
    history.push({
      role: "assistant",
      content: data.reply,
    });
  } catch (error) {
    appendMessage("assistant", `出错了:${error.message}`);
  } finally {
    sendBtn.disabled = false;
    sendBtn.textContent = "发送";
  }
}

sendBtn.addEventListener("click", sendMessage);

messageInput.addEventListener("keydown", function (event) {
  if (event.ctrlKey && event.key === "Enter") {
    sendMessage();
  }
});

六、编写 package.json

创建 package.json

{
  "name": "chatgpt-docker-demo",
  "version": "1.0.0",
  "description": "A simple ChatGPT web app deployed with Docker",
  "main": "src/server.js",
  "scripts": {
    "start": "node src/server.js",
    "dev": "node src/server.js"
  },
  "dependencies": {
    "dotenv": "^16.4.5",
    "express": "^4.18.3",
    "openai": "^4.47.1"
  }
}

这里主要依赖三个包:

依赖 作用
express 用于创建 HTTP 服务
openai OpenAI 官方 Node.js SDK
dotenv 用于读取环境变量配置

七、编写环境变量文件

创建 .env.example

PORT=3000
OPENAI_API_KEY=sk-your-api-key
OPENAI_MODEL=gpt-4o-mini

实际部署时,不建议直接修改 .env.example,而是复制一份 .env

cp .env.example .env

然后编辑 .env

vim .env

将其中的 OPENAI_API_KEY 替换成你自己的 Key:

PORT=3000
OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx
OPENAI_MODEL=gpt-4o-mini

八、编写 Dockerfile

创建 Dockerfile

FROM node:20-alpine

WORKDIR /app

COPY package*.json ./

RUN npm install --production

COPY src ./src

ENV NODE_ENV=production

EXPOSE 3000

CMD ["npm", "start"]

这个 Dockerfile 的含义如下:

  1. 使用 node:20-alpine 作为基础镜像;
  2. 设置容器工作目录为 /app
  3. 先复制 package.json,安装依赖;
  4. 再复制源码;
  5. 设置生产环境变量;
  6. 暴露容器端口 3000
  7. 启动 Node.js 服务。

之所以先复制 package.json 再复制源码,是为了利用 Docker 构建缓存。只要依赖文件不变,后续构建就不需要重复执行 npm install,可以明显提升构建速度。


九、编写 docker-compose.yml

创建 docker-compose.yml

services:
  chatgpt-web:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: chatgpt-web
    restart: unless-stopped
    ports:
      - "${PORT:-3000}:3000"
    env_file:
      - .env

该配置文件定义了一个服务 chatgpt-web,它会基于当前目录下的 Dockerfile 构建镜像,并将宿主机端口映射到容器内部的 3000 端口。

如果 .env 中设置:

PORT=3000

那么访问地址就是:

http://服务器IP:3000

十、本地运行测试

如果你想先在本地不使用 Docker 测试,可以执行:

npm install
npm start

启动成功后访问:

http://localhost:3000

如果页面正常显示,并且输入问题后可以收到 AI 回复,说明源码没有问题。


十一、使用 Docker 构建镜像

在项目根目录执行:

docker build -t chatgpt-docker-demo:1.0.0 .

构建完成后查看镜像:

docker images

你应该可以看到类似结果:

REPOSITORY              TAG       IMAGE ID       CREATED          SIZE
chatgpt-docker-demo     1.0.0     xxxxxxxxxxxx   10 seconds ago   180MB

十二、使用 Docker 运行容器

如果不使用 Docker Compose,也可以通过 docker run 启动:

docker run -d \
  --name chatgpt-web \
  --restart unless-stopped \
  -p 3000:3000 \
  --env-file .env \
  chatgpt-docker-demo:1.0.0

查看容器状态:

docker ps

查看日志:

docker logs -f chatgpt-web

停止容器:

docker stop chatgpt-web

删除容器:

docker rm chatgpt-web

十三、使用 Docker Compose 一键部署

更推荐使用 Docker Compose 部署,命令更简单,也更方便维护。

启动服务:

docker compose up -d

查看运行状态:

docker compose ps

查看日志:

docker compose logs -f

重启服务:

docker compose restart

停止服务:

docker compose down

如果修改了源码,需要重新构建并启动:

docker compose up -d --build

十四、服务器防火墙配置

如果你将项目部署在云服务器上,需要确保安全组或防火墙放行对应端口。

以 Ubuntu 的 UFW 为例:

sudo ufw allow 3000/tcp
sudo ufw reload
sudo ufw status

如果你使用的是阿里云、腾讯云、华为云等云服务器,还需要在云平台控制台的安全组中放行 3000 端口。

开放后可以通过浏览器访问:

http://你的服务器IP:3000

十五、使用 Nginx 反向代理

在生产环境中,不建议直接暴露 Node.js 服务端口。更常见的方式是使用 Nginx 反向代理,并配置 HTTPS。

假设你的域名是:

chat.example.com

Nginx 配置示例:

server {
    listen 80;
    server_name chat.example.com;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

保存配置后测试:

sudo nginx -t

重载 Nginx:

sudo systemctl reload nginx

如果需要 HTTPS,可以使用 Certbot 申请免费证书:

sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d chat.example.com

十六、增加访问密码

如果你的服务只是个人或团队内部使用,建议增加简单的访问密码,避免被陌生人消耗 API 额度。

一种简单做法是在前端请求时携带访问密钥,后端校验请求头。

.env 中增加:

ACCESS_TOKEN=your-secret-token

修改后端 /api/chat 接口前增加校验逻辑:

const ACCESS_TOKEN = process.env.ACCESS_TOKEN;

app.use("/api/chat", (req, res, next) => {
  if (!ACCESS_TOKEN) {
    return next();
  }

  const token = req.headers["x-access-token"];

  if (token !== ACCESS_TOKEN) {
    return res.status(401).json({
      error: "未授权访问",
    });
  }

  next();
});

前端请求时添加请求头:

headers: {
  "Content-Type": "application/json",
  "x-access-token": "your-secret-token"
}

不过这种方式仍然比较简单,因为前端代码可以被查看。更安全的方式是接入登录系统、网关鉴权、企业 SSO 或通过内网访问。


十七、常见问题排查

1. 页面可以打开,但发送消息失败

可以先查看浏览器控制台和容器日志:

docker compose logs -f

常见原因包括:

  • API Key 没有配置;
  • API Key 无效;
  • 服务器无法访问 OpenAI API;
  • 模型名称填写错误;
  • 账户余额或额度不足。

2. 容器启动后立即退出

查看日志:

docker logs chatgpt-web

可能原因:

  • package.json 缺失;
  • 依赖安装失败;
  • src/server.js 路径不正确;
  • 环境变量文件格式错误。

3. 访问服务器 IP 没反应

排查顺序如下:

  1. 容器是否运行:docker ps
  2. 端口是否映射:检查 docker compose ps
  3. 服务器防火墙是否放行;
  4. 云服务安全组是否放行;
  5. 应用监听端口是否正确。

4. 回复速度较慢

大模型接口响应速度受多种因素影响,包括:

  • 网络延迟;
  • 模型本身响应速度;
  • 输入内容长度;
  • 上下文历史长度;
  • 服务所在地区。

可以通过减少历史消息数量、使用更轻量模型、开启流式响应等方式优化体验。


十八、生产环境优化建议

本文示例项目适合学习和小规模使用,如果要用于正式生产环境,建议进一步完善以下能力:

1. 增加用户认证

可以接入账号密码登录、OAuth、企业微信、飞书、钉钉或 SSO,避免匿名用户滥用服务。

2. 增加限流机制

可以使用 Redis 记录用户请求次数,对单 IP、单用户、单会话进行限流。例如每分钟最多请求 10 次。

3. 增加日志与监控

建议记录接口耗时、错误率、请求次数、Token 消耗等信息,并接入 Prometheus、Grafana、ELK 等监控系统。

4. 支持流式输出

当前示例是等待模型完整回答后一次性返回。真实 ChatGPT 体验通常是逐字输出,可以使用 Server-Sent Events 或 WebSocket 实现流式响应。

5. 隐藏敏感信息

API Key 必须只保存在服务端,不应出现在前端代码、浏览器请求参数或公开仓库中。

6. 使用多阶段构建

对于更复杂的前端项目,可以使用 Docker 多阶段构建,先编译前端资源,再复制到运行镜像中,从而减小镜像体积。


十九、完整 README 示例

你可以在项目根目录创建 README.md,方便后续维护:

# ChatGPT Docker Demo

一个基于 Node.js、Express 和 Docker 的 ChatGPT 简易 Web 应用。

## 功能

- Web 聊天界面
- 调用 OpenAI Chat Completions API
- 支持 Docker 部署
- 支持 Docker Compose 一键启动

## 快速开始

复制环境变量:

```bash
cp .env.example .env

编辑 .env

PORT=3000
OPENAI_API_KEY=sk-your-api-key
OPENAI_MODEL=gpt-4o-mini

启动服务:

docker compose up -d --build

访问:

http://localhost:3000

查看日志:

docker compose logs -f

停止服务:

docker compose down


---

## 二十、总结

本文完整演示了如何使用 Docker 部署一个简易 ChatGPT Web 应用。从项目目录设计、后端接口开发、前端页面编写,到 Dockerfile、docker-compose.yml、环境变量配置、服务器部署、Nginx 反向代理和常见问题排查,基本覆盖了一个小型 AI 应用从开发到上线的核心流程。

Docker 部署最大的好处在于环境一致性。无论你是在本地电脑、测试服务器还是生产服务器上运行,只要 Docker 环境可用,就可以用几乎相同的方式启动项目。对于 ChatGPT 这类依赖外部 API 的应用来说,容器化还能让配置、迁移、升级、回滚更加清晰可控。

如果你只是个人使用,本文提供的版本已经足够入门;如果你准备将它用于团队或生产环境,则建议继续完善鉴权、限流、日志、监控、流式响应、敏感词过滤、上下文管理和成本统计等功能。基于本文的源码,你可以很容易扩展出企业内部 AI 助手、客服机器人、知识库问答系统、代码助手或办公自动化工具。
目录结构
全文