nodebbs 0.0.9 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -17
- package/dist/commands/db/import.d.ts +10 -0
- package/dist/commands/db/import.js +87 -0
- package/dist/commands/pack/index.js +3 -4
- package/dist/commands/rebuild/index.js +3 -3
- package/dist/commands/start/index.d.ts +1 -1
- package/dist/commands/start/index.js +70 -61
- package/dist/templates/docker-compose.lowmem.yml +3 -49
- package/dist/templates/docker-compose.yml +91 -34
- package/dist/templates/env +26 -11
- package/dist/utils/docker.d.ts +1 -1
- package/dist/utils/docker.js +2 -5
- package/dist/utils/env.d.ts +1 -1
- package/dist/utils/selection.d.ts +1 -1
- package/dist/utils/selection.js +5 -6
- package/oclif.manifest.json +93 -59
- package/package.json +1 -1
- package/dist/templates/docker-compose.prod.yml +0 -120
- package/dist/templates/init-db.sql +0 -14
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ NodeBBS CLI 是一个专为全栈开发者设计的命令行工具,用于简
|
|
|
15
15
|
- 🎯 **全栈友好** - 命令设计贴近开发者思维
|
|
16
16
|
- 📊 **实时日志** - 方便查看各服务日志
|
|
17
17
|
- 💾 **数据库管理** - 内置数据库迁移和管理工具
|
|
18
|
-
- 🌐 **内置模板** -
|
|
18
|
+
- 🌐 **内置模板** - 无需本地配置文件即可通过远程镜像快速部署
|
|
19
19
|
|
|
20
20
|
## 📦 安装
|
|
21
21
|
|
|
@@ -46,7 +46,7 @@ npx nodebbs [command]
|
|
|
46
46
|
npx nodebbs
|
|
47
47
|
```
|
|
48
48
|
- **start**: 开始部署(首次使用推荐选择此项)
|
|
49
|
-
- **rebuild**:
|
|
49
|
+
- **rebuild**: 更新并重启(Pull & Restart,代码/镜像更新后使用)
|
|
50
50
|
|
|
51
51
|
## 📚 命令参考
|
|
52
52
|
|
|
@@ -60,21 +60,25 @@ npx nodebbs start
|
|
|
60
60
|
# 指定环境启动
|
|
61
61
|
npx nodebbs start -e production
|
|
62
62
|
|
|
63
|
-
# 重新构建并启动(跳过检查)
|
|
64
|
-
npx nodebbs start --build
|
|
65
|
-
```
|
|
66
|
-
|
|
67
63
|
**参数**:
|
|
68
|
-
- `-e, --env` - 部署环境(production, lowmem
|
|
69
|
-
- `-
|
|
64
|
+
- `-e, --env` - 部署环境(production, lowmem)
|
|
65
|
+
- `-t, --tag` - 镜像版本 tag (默认: latest)
|
|
70
66
|
|
|
71
67
|
启动流程包含:
|
|
72
68
|
- Docker 环境检查
|
|
73
69
|
- 环境变量验证
|
|
74
|
-
-
|
|
70
|
+
- 拉取最新镜像
|
|
75
71
|
- 服务启动
|
|
76
|
-
-
|
|
77
|
-
-
|
|
72
|
+
- 健康检查(默认开启)
|
|
73
|
+
- 数据库初始化(默认开启)
|
|
74
|
+
|
|
75
|
+
#### `nodebbs rebuild`
|
|
76
|
+
拉取最新镜像并重启服务 (Update & Restart)
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
npx nodebbs rebuild
|
|
80
|
+
```
|
|
81
|
+
> 此命令是 `start` 的别名,用于快速更新部署。
|
|
78
82
|
|
|
79
83
|
#### `nodebbs restart`
|
|
80
84
|
重启所有服务(不更新镜像,支持环境选择)
|
|
@@ -272,9 +276,8 @@ vi .env
|
|
|
272
276
|
|
|
273
277
|
### 支持的环境
|
|
274
278
|
|
|
275
|
-
- **basic** - 基础环境(仅用于测试)
|
|
276
279
|
- **lowmem** - 低配环境(1C1G/1C2G)
|
|
277
|
-
- **production** -
|
|
280
|
+
- **production** - 生产环境(标准配置,推荐)
|
|
278
281
|
|
|
279
282
|
### 环境变量
|
|
280
283
|
|
|
@@ -309,7 +312,7 @@ CLI 会自动记住您上次启动的环境:
|
|
|
309
312
|
2. 后续运行 `nodebbs logs`, `nodebbs status` 等命令时,会自动使用该环境,无需再次指定 `-e production`。
|
|
310
313
|
3. 运行 `nodebbs stop` 成功停止服务后,会自动删除 `.nodebbs-env` 文件。
|
|
311
314
|
|
|
312
|
-
**注意**:如果您需要临时操作其他环境,仍然可以使用 `-e` 参数强制指定,例如 `nodebbs logs -e
|
|
315
|
+
**注意**:如果您需要临时操作其他环境,仍然可以使用 `-e` 参数强制指定,例如 `nodebbs logs -e lowmem`。
|
|
313
316
|
|
|
314
317
|
## 🛠️ 高级用法
|
|
315
318
|
|
|
@@ -330,9 +333,8 @@ CLI 会自动:
|
|
|
330
333
|
### 自定义配置
|
|
331
334
|
|
|
332
335
|
如果需要自定义配置,在项目根目录创建:
|
|
333
|
-
- `docker-compose.yml` -
|
|
334
|
-
- `docker-compose.
|
|
335
|
-
- `docker-compose.lowmem.yml` - 低配环境配置
|
|
336
|
+
- `docker-compose.yml` - 基础/生产环境配置
|
|
337
|
+
- `docker-compose.lowmem.yml` - 低配环境配置 (Override)
|
|
336
338
|
|
|
337
339
|
CLI 会优先使用本地配置文件。
|
|
338
340
|
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class DbImport extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static flags: {
|
|
5
|
+
env: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
6
|
+
file: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
+
yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
8
|
+
};
|
|
9
|
+
run(): Promise<void>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { Command, Flags } from '@oclif/core';
|
|
2
|
+
import { confirm } from '@inquirer/prompts';
|
|
3
|
+
import { getComposeFiles } from '../../utils/docker.js';
|
|
4
|
+
import { logger } from '../../utils/logger.js';
|
|
5
|
+
import { execa } from 'execa';
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import dotenv from 'dotenv';
|
|
9
|
+
import { EnvFlag, selectEnvironment } from '../../utils/selection.js';
|
|
10
|
+
export default class DbImport extends Command {
|
|
11
|
+
static description = '导入数据库 (PostgreSQL)';
|
|
12
|
+
static flags = {
|
|
13
|
+
env: EnvFlag,
|
|
14
|
+
file: Flags.string({
|
|
15
|
+
char: 'f',
|
|
16
|
+
description: 'SQL 备份文件路径',
|
|
17
|
+
required: true,
|
|
18
|
+
}),
|
|
19
|
+
yes: Flags.boolean({
|
|
20
|
+
char: 'y',
|
|
21
|
+
description: '跳过确认提示',
|
|
22
|
+
default: false,
|
|
23
|
+
}),
|
|
24
|
+
};
|
|
25
|
+
async run() {
|
|
26
|
+
const { flags } = await this.parse(DbImport);
|
|
27
|
+
// 1. 选择环境
|
|
28
|
+
const env = await selectEnvironment(flags.env);
|
|
29
|
+
// 2. 验证文件
|
|
30
|
+
const inputPath = path.resolve(process.cwd(), flags.file);
|
|
31
|
+
if (!fs.existsSync(inputPath)) {
|
|
32
|
+
logger.error(`找不到备份文件: ${inputPath}`);
|
|
33
|
+
this.exit(1);
|
|
34
|
+
}
|
|
35
|
+
// 3. 用户确认
|
|
36
|
+
if (!flags.yes) {
|
|
37
|
+
logger.warning('警告:此操作将覆盖当前数据库中的数据!');
|
|
38
|
+
const confirmImport = await confirm({
|
|
39
|
+
message: `确认从 ${flags.file} 恢复数据库?`,
|
|
40
|
+
default: false
|
|
41
|
+
});
|
|
42
|
+
if (!confirmImport) {
|
|
43
|
+
logger.info('操作已取消。');
|
|
44
|
+
this.exit(0);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// 4. 获取 Compose 文件
|
|
48
|
+
const { files, isBuiltIn } = await getComposeFiles(env);
|
|
49
|
+
logger.info(`正在从文件恢复数据库: ${inputPath}`);
|
|
50
|
+
logger.info('环境: ' + env);
|
|
51
|
+
// 加载环境配置
|
|
52
|
+
const envConfig = dotenv.config().parsed || {};
|
|
53
|
+
const dbUser = envConfig.POSTGRES_USER || 'postgres';
|
|
54
|
+
const dbName = envConfig.POSTGRES_DB || 'nodebbs';
|
|
55
|
+
const dbPassword = envConfig.POSTGRES_PASSWORD;
|
|
56
|
+
// 5. 构建 Compose 参数
|
|
57
|
+
const composeArgs = files.flatMap(f => ['-f', f]);
|
|
58
|
+
if (isBuiltIn) {
|
|
59
|
+
composeArgs.push('--project-directory', process.cwd());
|
|
60
|
+
}
|
|
61
|
+
// 6. 运行 psql
|
|
62
|
+
// const dumpArgs = [...composeArgs, 'exec', '-T'] // backup uses exec
|
|
63
|
+
// import should also use exec -T to accept stdin
|
|
64
|
+
const importArgs = [...composeArgs, 'exec', '-T'];
|
|
65
|
+
// 如果有密码则注入
|
|
66
|
+
if (dbPassword) {
|
|
67
|
+
importArgs.push('-e', `PGPASSWORD=${dbPassword}`);
|
|
68
|
+
}
|
|
69
|
+
importArgs.push('postgres', 'psql', '-U', dbUser, '-d', dbName);
|
|
70
|
+
try {
|
|
71
|
+
const subprocess = execa('docker', ['compose', ...importArgs], {
|
|
72
|
+
input: fs.createReadStream(inputPath)
|
|
73
|
+
});
|
|
74
|
+
// subprocess.stdout?.pipe(process.stdout)
|
|
75
|
+
// subprocess.stderr?.pipe(process.stderr)
|
|
76
|
+
await subprocess;
|
|
77
|
+
logger.success('数据库导入成功!');
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
logger.error('数据库导入失败');
|
|
81
|
+
if (error instanceof Error) {
|
|
82
|
+
logger.error(error.message);
|
|
83
|
+
}
|
|
84
|
+
this.exit(1);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -74,9 +74,6 @@ export default class Pack extends Command {
|
|
|
74
74
|
return line;
|
|
75
75
|
});
|
|
76
76
|
fs.writeFileSync(path.join(tmpDir, 'docker-compose.yml'), cleanedLines.join('\n'));
|
|
77
|
-
if (fs.existsSync('docker-compose.prod.yml')) {
|
|
78
|
-
fs.copyFileSync('docker-compose.prod.yml', path.join(tmpDir, 'docker-compose.prod.yml'));
|
|
79
|
-
}
|
|
80
77
|
if (fs.existsSync('.env')) {
|
|
81
78
|
this.warn('检测到 .env 文件,出于安全考虑,不会默认打包 .env 文件。请在部署时手动配置环境变量。');
|
|
82
79
|
}
|
|
@@ -117,7 +114,9 @@ services:
|
|
|
117
114
|
image: nodebbs-web:latest
|
|
118
115
|
EOF
|
|
119
116
|
|
|
120
|
-
|
|
117
|
+
COMPOSE_FILES="-f docker-compose.yml -f docker-compose.override.yml"
|
|
118
|
+
|
|
119
|
+
docker compose \$COMPOSE_FILES up -d
|
|
121
120
|
|
|
122
121
|
echo "部署完成!"
|
|
123
122
|
`;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { Command } from '@oclif/core';
|
|
2
2
|
import Start from '../start/index.js';
|
|
3
3
|
export default class Rebuild extends Command {
|
|
4
|
-
static description = '
|
|
4
|
+
static description = '拉取最新镜像并重启服务 (Update & Restart)';
|
|
5
5
|
// Allow passing flags like -e to the underlying start command
|
|
6
6
|
static strict = false;
|
|
7
7
|
async run() {
|
|
8
|
-
// Invoke Start command
|
|
9
|
-
await Start.run(
|
|
8
|
+
// Invoke Start command to pull latest images and restart
|
|
9
|
+
await Start.run(this.argv);
|
|
10
10
|
}
|
|
11
11
|
}
|
|
@@ -3,7 +3,7 @@ export default class Start extends Command {
|
|
|
3
3
|
static description: string;
|
|
4
4
|
static flags: {
|
|
5
5
|
env: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
6
|
-
|
|
6
|
+
tag: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
7
|
};
|
|
8
8
|
run(): Promise<void>;
|
|
9
9
|
}
|
|
@@ -4,25 +4,20 @@ import { checkDocker, runCompose, waitForHealth, execCompose, getComposeFiles }
|
|
|
4
4
|
import { initEnv, checkEnv } from '../../utils/env.js';
|
|
5
5
|
import { logger } from '../../utils/logger.js';
|
|
6
6
|
import dotenv from 'dotenv';
|
|
7
|
+
import { input } from '@inquirer/prompts';
|
|
7
8
|
import { EnvFlag, selectEnvironment } from '../../utils/selection.js';
|
|
8
9
|
export default class Start extends Command {
|
|
9
10
|
static description = '开始部署';
|
|
10
11
|
static flags = {
|
|
11
12
|
env: EnvFlag,
|
|
12
|
-
|
|
13
|
-
char: '
|
|
14
|
-
description: '
|
|
15
|
-
default: false,
|
|
13
|
+
tag: Flags.string({
|
|
14
|
+
char: 't',
|
|
15
|
+
description: 'Image version tag (e.g. latest, v0.1.0)',
|
|
16
16
|
}),
|
|
17
17
|
};
|
|
18
18
|
async run() {
|
|
19
19
|
const { flags } = await this.parse(Start);
|
|
20
|
-
|
|
21
|
-
logger.header('NodeBBS 重新构建启动');
|
|
22
|
-
}
|
|
23
|
-
else {
|
|
24
|
-
logger.header('NodeBBS Docker 部署');
|
|
25
|
-
}
|
|
20
|
+
logger.header('NodeBBS Docker 部署');
|
|
26
21
|
// 1. 选择环境
|
|
27
22
|
const env = await selectEnvironment(flags.env);
|
|
28
23
|
// 2. 获取 Compose 文件
|
|
@@ -39,69 +34,83 @@ export default class Start extends Command {
|
|
|
39
34
|
else if (env === 'lowmem') {
|
|
40
35
|
logger.success('已选择:低配环境');
|
|
41
36
|
}
|
|
42
|
-
else {
|
|
43
|
-
logger.success('已选择:基础环境');
|
|
44
|
-
if (!flags.build) {
|
|
45
|
-
logger.warning('注意:无资源限制,不推荐用于生产环境。');
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
37
|
// 3. 检查 Docker 和环境变量
|
|
49
38
|
await checkDocker();
|
|
50
39
|
// initEnv 保证 .env 存在(或退出)
|
|
51
40
|
await initEnv();
|
|
52
41
|
await checkEnv(env);
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
42
|
+
// 如果不是本地构建,则询问镜像版本
|
|
43
|
+
// 先加载环境变量以获取当前的 IMAGE 配置(如果有)
|
|
44
|
+
const envConfig = dotenv.config().parsed || {};
|
|
45
|
+
let tag = flags.tag;
|
|
46
|
+
if (!tag) {
|
|
47
|
+
tag = await input({
|
|
48
|
+
message: '请选择要部署的镜像版本:',
|
|
49
|
+
default: 'latest'
|
|
57
50
|
});
|
|
58
|
-
if (!continueDeploy) {
|
|
59
|
-
logger.info('操作已取消。');
|
|
60
|
-
this.exit(0);
|
|
61
|
-
}
|
|
62
51
|
}
|
|
63
|
-
//
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
52
|
+
// 构造新的镜像名称
|
|
53
|
+
// 逻辑:如果 .env 里定义了 API_IMAGE,则解析出 registry/name 部分,然后拼接 tag
|
|
54
|
+
// 否则使用默认 registry
|
|
55
|
+
const defaultApiImage = 'ghcr.io/aiprojecthub/nodebbs-api';
|
|
56
|
+
const defaultWebImage = 'ghcr.io/aiprojecthub/nodebbs-web';
|
|
57
|
+
let currentApiImage = envConfig.API_IMAGE || defaultApiImage;
|
|
58
|
+
let currentWebImage = envConfig.WEB_IMAGE || defaultWebImage;
|
|
59
|
+
const replaceTag = (image, newTag) => {
|
|
60
|
+
const parts = image.split(':');
|
|
61
|
+
if (parts.length > 1 && !parts[parts.length - 1].includes('/')) {
|
|
62
|
+
// 最后一个部分不包含 '/',认为是 tag
|
|
63
|
+
parts.pop();
|
|
64
|
+
}
|
|
65
|
+
return `${parts.join(':')}:${newTag}`;
|
|
66
|
+
};
|
|
67
|
+
process.env.API_IMAGE = replaceTag(currentApiImage, tag);
|
|
68
|
+
process.env.WEB_IMAGE = replaceTag(currentWebImage, tag);
|
|
69
|
+
logger.info(`将部署版本: ${tag}`);
|
|
70
|
+
logger.info(`API Image: ${process.env.API_IMAGE}`);
|
|
71
|
+
logger.info(`Web Image: ${process.env.WEB_IMAGE}`);
|
|
72
|
+
const continueDeploy = await confirm({
|
|
73
|
+
message: '是否继续启动?',
|
|
74
|
+
default: true
|
|
75
|
+
});
|
|
76
|
+
if (!continueDeploy) {
|
|
77
|
+
logger.info('操作已取消。');
|
|
78
|
+
this.exit(0);
|
|
68
79
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
80
|
+
// 4. 拉取并启动服务
|
|
81
|
+
logger.info('正在拉取最新的 Docker 镜像...');
|
|
82
|
+
await runCompose(composeFiles, ['pull'], isBuiltIn);
|
|
83
|
+
logger.success('镜像拉取完成');
|
|
84
|
+
logger.info('正在启动服务...');
|
|
85
|
+
await runCompose(composeFiles, ['up', '-d'], isBuiltIn);
|
|
86
|
+
logger.success('服务启动指令已发送');
|
|
87
|
+
// 5. 启动后操作
|
|
88
|
+
await waitForHealth(composeFiles, isBuiltIn);
|
|
89
|
+
const pushDb = await confirm({
|
|
90
|
+
message: '是否推送数据库 schema?',
|
|
91
|
+
default: false
|
|
92
|
+
});
|
|
93
|
+
if (pushDb) {
|
|
94
|
+
logger.info('正在推送数据库 schema...');
|
|
95
|
+
await execCompose(composeFiles, 'api', ['npm', 'run', 'db:push'], isBuiltIn);
|
|
96
|
+
logger.success('数据库 schema 推送完成');
|
|
76
97
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
logger.info('正在推送数据库 schema...');
|
|
86
|
-
await execCompose(composeFiles, 'api', ['npm', 'run', 'db:push'], isBuiltIn);
|
|
87
|
-
logger.success('数据库 schema 推送完成');
|
|
88
|
-
}
|
|
89
|
-
const seedDb = await confirm({
|
|
90
|
-
message: '是否初始化种子数据?',
|
|
91
|
-
default: false
|
|
92
|
-
});
|
|
93
|
-
if (seedDb) {
|
|
94
|
-
logger.info('正在初始化数据...');
|
|
95
|
-
await execCompose(composeFiles, 'api', ['npm', 'run', 'seed'], isBuiltIn);
|
|
96
|
-
logger.success('数据初始化完成');
|
|
97
|
-
}
|
|
98
|
+
const seedDb = await confirm({
|
|
99
|
+
message: '是否初始化种子数据?',
|
|
100
|
+
default: false
|
|
101
|
+
});
|
|
102
|
+
if (seedDb) {
|
|
103
|
+
logger.info('正在初始化数据...');
|
|
104
|
+
await execCompose(composeFiles, 'api', ['npm', 'run', 'seed'], isBuiltIn);
|
|
105
|
+
logger.success('数据初始化完成');
|
|
98
106
|
}
|
|
99
|
-
logger.header(
|
|
107
|
+
logger.header('NodeBBS 启动成功!');
|
|
100
108
|
// 6. 显示信息
|
|
101
109
|
logger.info(`环境: ${env}`);
|
|
102
|
-
|
|
103
|
-
const
|
|
104
|
-
const
|
|
110
|
+
// 复用之前的 envConfig 或重新读取 (这里直接复用即可,或者为了保险重新读取但不要 redeclare)
|
|
111
|
+
const finalEnvConfig = dotenv.config().parsed || {};
|
|
112
|
+
const webPort = finalEnvConfig.WEB_PORT || '3100';
|
|
113
|
+
const apiPort = finalEnvConfig.API_PORT || '7100';
|
|
105
114
|
console.log(` Web 前端: http://localhost:${webPort}`);
|
|
106
115
|
console.log(` API 服务: http://localhost:${apiPort}`);
|
|
107
116
|
console.log(` API 文档: http://localhost:${apiPort}/docs`);
|
|
@@ -1,19 +1,6 @@
|
|
|
1
|
-
# 低内存环境 Docker Compose 覆盖配置
|
|
2
|
-
# 适用于 1C1G 或 1C2G 的低配服务器
|
|
3
|
-
# 使用方式: docker compose -f docker-compose.yml -f docker-compose.lowmem.yml up -d
|
|
4
|
-
|
|
5
1
|
services:
|
|
6
2
|
# PostgreSQL 数据库 - 低内存优化
|
|
7
3
|
postgres:
|
|
8
|
-
restart: always
|
|
9
|
-
ports: [] # 不暴露端口到主机,只在内部网络访问
|
|
10
|
-
environment:
|
|
11
|
-
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
|
12
|
-
healthcheck:
|
|
13
|
-
interval: 30s
|
|
14
|
-
timeout: 10s
|
|
15
|
-
retries: 5
|
|
16
|
-
start_period: 40s
|
|
17
4
|
deploy:
|
|
18
5
|
resources:
|
|
19
6
|
limits:
|
|
@@ -23,14 +10,13 @@ services:
|
|
|
23
10
|
cpus: '0.1'
|
|
24
11
|
memory: 128M
|
|
25
12
|
logging:
|
|
26
|
-
driver: "json-file"
|
|
27
13
|
options:
|
|
28
14
|
max-size: "5m"
|
|
29
15
|
max-file: "2"
|
|
30
16
|
|
|
31
17
|
# Redis 缓存 - 低内存优化
|
|
32
18
|
redis:
|
|
33
|
-
|
|
19
|
+
# 降低内存限制
|
|
34
20
|
command: >
|
|
35
21
|
redis-server
|
|
36
22
|
--requirepass ${REDIS_PASSWORD}
|
|
@@ -40,12 +26,6 @@ services:
|
|
|
40
26
|
--maxmemory-policy allkeys-lru
|
|
41
27
|
--save 900 1
|
|
42
28
|
--save 300 10
|
|
43
|
-
ports: [] # 不暴露端口到主机
|
|
44
|
-
healthcheck:
|
|
45
|
-
interval: 30s
|
|
46
|
-
timeout: 10s
|
|
47
|
-
retries: 5
|
|
48
|
-
start_period: 20s
|
|
49
29
|
deploy:
|
|
50
30
|
resources:
|
|
51
31
|
limits:
|
|
@@ -55,23 +35,15 @@ services:
|
|
|
55
35
|
cpus: '0.1'
|
|
56
36
|
memory: 64M
|
|
57
37
|
logging:
|
|
58
|
-
driver: "json-file"
|
|
59
38
|
options:
|
|
60
39
|
max-size: "5m"
|
|
61
40
|
max-file: "2"
|
|
62
41
|
|
|
63
42
|
# API 服务 - 低内存优化
|
|
64
43
|
api:
|
|
65
|
-
restart: always
|
|
66
44
|
environment:
|
|
67
|
-
|
|
68
|
-
JWT_SECRET: ${JWT_SECRET}
|
|
69
|
-
CORS_ORIGIN: ${CORS_ORIGIN}
|
|
70
|
-
APP_URL: ${APP_URL}
|
|
71
|
-
# Node.js 内存限制
|
|
45
|
+
# 降低 Node.js 内存限制
|
|
72
46
|
NODE_OPTIONS: "--max-old-space-size=384"
|
|
73
|
-
volumes:
|
|
74
|
-
- api_uploads:/app/apps/api/uploads
|
|
75
47
|
healthcheck:
|
|
76
48
|
start_period: 90s # 低内存环境启动更慢
|
|
77
49
|
interval: 60s
|
|
@@ -84,22 +56,13 @@ services:
|
|
|
84
56
|
cpus: '0.2'
|
|
85
57
|
memory: 256M
|
|
86
58
|
logging:
|
|
87
|
-
driver: "json-file"
|
|
88
59
|
options:
|
|
89
60
|
max-size: "10m"
|
|
90
|
-
max-file: "3"
|
|
91
61
|
|
|
92
62
|
# Web 前端服务 - 低内存优化
|
|
93
63
|
web:
|
|
94
|
-
restart: always
|
|
95
|
-
build:
|
|
96
|
-
args:
|
|
97
|
-
NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL}
|
|
98
|
-
NEXT_PUBLIC_APP_URL: ${NEXT_PUBLIC_APP_URL}
|
|
99
64
|
environment:
|
|
100
|
-
|
|
101
|
-
NEXT_PUBLIC_APP_URL: ${NEXT_PUBLIC_APP_URL}
|
|
102
|
-
# Node.js 内存限制
|
|
65
|
+
# 降低 Node.js 内存限制
|
|
103
66
|
NODE_OPTIONS: "--max-old-space-size=384"
|
|
104
67
|
healthcheck:
|
|
105
68
|
start_period: 90s # 低内存环境启动更慢
|
|
@@ -113,14 +76,5 @@ services:
|
|
|
113
76
|
cpus: '0.2'
|
|
114
77
|
memory: 256M
|
|
115
78
|
logging:
|
|
116
|
-
driver: "json-file"
|
|
117
79
|
options:
|
|
118
80
|
max-size: "10m"
|
|
119
|
-
max-file: "3"
|
|
120
|
-
|
|
121
|
-
# 网络配置
|
|
122
|
-
networks:
|
|
123
|
-
nodebbs-network:
|
|
124
|
-
ipam:
|
|
125
|
-
config:
|
|
126
|
-
- subnet: 172.28.0.0/16
|