从零搭建一个可 Docker 一键部署的 ChatGPT Web 应用,完整源码实战教程
ChatGPT Docker部署教程|附源码
随着大模型应用的普及,越来越多开发者希望将 ChatGPT 能力集成到自己的业务系统、内部工具、知识库问答平台或个人项目中。相比直接在本地运行项目,使用 Docker 部署具有环境一致、迁移方便、启动快速、便于运维等优势。本文将从零开始介绍如何使用 Docker 部署一个简单的 ChatGPT Web 服务,并附带完整源码示例,包括后端接口、前端页面、Dockerfile、docker-compose 配置以及环境变量说明。
本教程适合有一定 Linux、Docker、Node.js 基础的开发者阅读。如果你是初学者,也可以按照步骤逐条执行,基本能够顺利完成部署。
一、项目目标
本文最终实现一个轻量级 ChatGPT Web 应用,具备以下功能:
- 提供一个网页聊天界面;
- 用户在页面输入问题;
- 后端调用 OpenAI Chat Completions API;
- 将模型回复返回给前端展示;
- 支持通过 Docker 一键部署;
- 支持通过环境变量配置 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 实现。后端主要负责三件事:
- 提供静态网页;
- 接收前端聊天请求;
- 调用 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 简易聊天应用
创建 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 的含义如下:
- 使用
node:20-alpine作为基础镜像; - 设置容器工作目录为
/app; - 先复制
package.json,安装依赖; - 再复制源码;
- 设置生产环境变量;
- 暴露容器端口
3000; - 启动 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 没反应
排查顺序如下:
- 容器是否运行:
docker ps; - 端口是否映射:检查
docker compose ps; - 服务器防火墙是否放行;
- 云服务安全组是否放行;
- 应用监听端口是否正确。
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 助手、客服机器人、知识库问答系统、代码助手或办公自动化工具。