nodebbs 0.2.0 → 0.3.0-beta.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 +64 -307
- package/dist/commands/db/import.d.ts +9 -0
- package/dist/commands/db/import.js +115 -0
- package/dist/commands/pack/index.js +60 -45
- package/dist/commands/restart/index.js +13 -7
- package/dist/commands/start/index.js +29 -10
- package/dist/commands/upgrade/index.d.ts +8 -0
- package/dist/commands/upgrade/index.js +39 -0
- package/dist/interactive.js +1 -1
- package/dist/templates/docker-compose.build.yml +10 -0
- package/dist/templates/docker-compose.lowmem.yml +10 -45
- package/dist/templates/docker-compose.prod.yml +11 -42
- package/dist/templates/docker-compose.yml +7 -9
- package/dist/templates/dockerignore +120 -0
- package/dist/templates/env +6 -10
- package/dist/utils/docker.js +45 -12
- package/dist/utils/env.d.ts +5 -0
- package/dist/utils/env.js +90 -16
- package/dist/utils/selection.d.ts +2 -0
- package/dist/utils/selection.js +17 -19
- package/oclif.manifest.json +122 -65
- package/package.json +1 -1
- package/dist/commands/rebuild/index.d.ts +0 -6
- package/dist/commands/rebuild/index.js +0 -11
- package/dist/templates/init-db.sql +0 -14
|
@@ -4,7 +4,8 @@ import * as fs from 'fs';
|
|
|
4
4
|
import * as path from 'path';
|
|
5
5
|
import * as os from 'os';
|
|
6
6
|
import { getTemplatePath } from '../../utils/template.js';
|
|
7
|
-
import { checkDocker } from '../../utils/docker.js';
|
|
7
|
+
import { checkDocker, getComposeFiles, runCompose } from '../../utils/docker.js';
|
|
8
|
+
import { getDeploymentMode } from '../../utils/selection.js';
|
|
8
9
|
export default class Pack extends Command {
|
|
9
10
|
static description = '生成离线部署包';
|
|
10
11
|
static flags = {
|
|
@@ -19,63 +20,66 @@ export default class Pack extends Command {
|
|
|
19
20
|
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'nodebbs-pack-'));
|
|
20
21
|
this.log(`正在准备构建环境: ${tmpDir}`);
|
|
21
22
|
try {
|
|
23
|
+
const mode = await getDeploymentMode();
|
|
24
|
+
if (mode !== 'source') {
|
|
25
|
+
this.error('打包命令仅支持源码部署模式。请在 NodeBBS 项目根目录下运行 (需要包含 package.json 和源代码)。');
|
|
26
|
+
}
|
|
22
27
|
// 2. 导出配置文件
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
if (!fs.existsSync('docker-compose.yml')) {
|
|
29
|
-
// TODO: 从 templates 复制 (简化起见,我们假设用户在项目根目录,或者我们强制要求)
|
|
30
|
-
// 如果没找到,我们实际上无法 build api/web,因为 build context 需要源码。
|
|
31
|
-
// 所以必须要求在项目根目录运行。
|
|
32
|
-
this.error('请在 NodeBBS 项目根目录下运行此命令 (需要包含 docker-compose.yml 和源代码)。');
|
|
28
|
+
// 使用 getComposeFiles 获取准确的配置文件路径 (包含 base 和 override)
|
|
29
|
+
// 默认打包生产环境配置
|
|
30
|
+
const { files, isBuiltIn } = await getComposeFiles('production');
|
|
31
|
+
if (isBuiltIn) {
|
|
32
|
+
console.warn('当前目录未找到 docker-compose.yml,将使用内置模板。');
|
|
33
33
|
}
|
|
34
|
-
// 3.
|
|
35
|
-
this.log('正在构建应用镜像 (
|
|
36
|
-
|
|
34
|
+
// 3. 构建应用镜像
|
|
35
|
+
this.log('正在构建应用镜像 (这可能需要几分钟)...');
|
|
36
|
+
// 注意:如果使用内置模板,context 可能是错的,但 build 命令通常需要 context。
|
|
37
|
+
// getComposeFiles 返回的 file path 是绝对路径。
|
|
38
|
+
// 如果是 isBuiltIn,说明用的是 cli/templates/docker-compose.yml
|
|
39
|
+
// 在 source 模式下,如果必须用内置模板作为 base,我们需要确保 build context 正确。
|
|
40
|
+
// 如果我们只是为了获取 content,那没问题。但 docker compose build 需要 context。
|
|
41
|
+
// 我们之前 manual check 的时候,如果 missing local file, we fallback to template path.
|
|
42
|
+
// runCompose 会 handle -f arguments.
|
|
43
|
+
// 这里为了简单,我们直接运行 build。
|
|
44
|
+
// 但要注意,如果 isBuiltIn,runCompose 会自动加 --project-directory cwd
|
|
45
|
+
// 这里我们主要目的是打包,所以 build 应该基于 cwd。
|
|
46
|
+
// 原有逻辑直接 execa('docker', ['compose', 'build']),这依赖于 cwd 下有 docker-compose.yml。
|
|
47
|
+
// 如果 cwd 下没有,我们需要传 -f。
|
|
48
|
+
// 所以这里应该使用 runCompose 来执行 build,以确保参数一致性
|
|
49
|
+
await runCompose(files, ['build'], isBuiltIn);
|
|
37
50
|
// 4. 拉取依赖镜像
|
|
38
51
|
this.log('正在确保数据库镜像已下载...');
|
|
39
52
|
await execa('docker', ['pull', 'postgres:16-alpine'], { stdio: 'inherit' });
|
|
40
53
|
await execa('docker', ['pull', 'redis:7-alpine'], { stdio: 'inherit' });
|
|
41
54
|
// 5. 导出镜像
|
|
42
55
|
this.log('正在导出镜像到文件 (nodebbs-images.tar)...');
|
|
43
|
-
// 获取实际的镜像名,这里假设 docker-compose build 生成的镜像名符合预期
|
|
44
|
-
// 通常是 <dir>_api 和 <dir>_web,或者我们在 compose 文件里指定了 image name?
|
|
45
|
-
// 查看 template: 没有指定 image name,所以默认为 ${dirname}-api。
|
|
46
|
-
// 为了确保准确,我们可以解析 docker compose config 的输出,或者强制指定 image name。
|
|
47
|
-
// 更稳妥的方式:直接 save 指定的服务镜像。
|
|
48
|
-
// docker compose images -q 可以获取 ID。
|
|
49
56
|
// 获取所有相关镜像的列表
|
|
50
57
|
const projectName = path.basename(process.cwd()).toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
51
|
-
|
|
52
|
-
'postgres:16-alpine',
|
|
53
|
-
'redis:7-alpine',
|
|
54
|
-
`${projectName}-api`, // 默认命名规则
|
|
55
|
-
`${projectName}-web`
|
|
56
|
-
];
|
|
57
|
-
// 我们最好通过 docker compose config 来确认镜像名,但这比较复杂。
|
|
58
|
+
// 我们最好通过 docker compose config 来确认镜像名
|
|
58
59
|
// 简单策略:先 save postgres 和 redis,对于 api 和 web,我们先 tag 一下以确保名字固定。
|
|
59
60
|
await execa('docker', ['tag', `${projectName}-api`, 'nodebbs-api:latest']);
|
|
60
61
|
await execa('docker', ['tag', `${projectName}-web`, 'nodebbs-web:latest']);
|
|
61
62
|
const imagesToSave = ['postgres:16-alpine', 'redis:7-alpine', 'nodebbs-api:latest', 'nodebbs-web:latest'];
|
|
62
63
|
await execa('docker', ['save', '-o', path.join(tmpDir, 'nodebbs-images.tar'), ...imagesToSave], { stdio: 'inherit' });
|
|
63
|
-
// 6. 复制配置文件 (并进行修改)
|
|
64
|
-
//
|
|
65
|
-
//
|
|
66
|
-
|
|
67
|
-
let composeContent = fs.readFileSync(
|
|
64
|
+
// 6. 复制配置文件 (并进行修改) - 移除源码挂载,适配离线环境
|
|
65
|
+
// files[0] -> Base
|
|
66
|
+
// files[1] -> Override (Optional)
|
|
67
|
+
const baseFile = files[0];
|
|
68
|
+
let composeContent = fs.readFileSync(baseFile, 'utf-8');
|
|
68
69
|
const lines = composeContent.split('\n');
|
|
69
70
|
const cleanedLines = lines.map(line => {
|
|
70
71
|
// 移除 api 和 web 的源码挂载
|
|
71
|
-
if (line.
|
|
72
|
+
if (line.match(/- \.\/apps\/.*\/src:\/app\/apps\/.*\/src/)) {
|
|
72
73
|
return line.replace(/^/, '# [OFFLINE-PACK-REMOVED] ');
|
|
73
74
|
}
|
|
74
75
|
return line;
|
|
75
76
|
});
|
|
76
77
|
fs.writeFileSync(path.join(tmpDir, 'docker-compose.yml'), cleanedLines.join('\n'));
|
|
77
|
-
|
|
78
|
-
|
|
78
|
+
// 处理 Override 文件 (files[1])
|
|
79
|
+
// 我们的 install.sh 期望 docker-compose.prod.yml
|
|
80
|
+
if (files.length > 1) {
|
|
81
|
+
const overrideFile = files[1];
|
|
82
|
+
fs.copyFileSync(overrideFile, path.join(tmpDir, 'docker-compose.prod.yml'));
|
|
79
83
|
}
|
|
80
84
|
if (fs.existsSync('.env')) {
|
|
81
85
|
this.warn('检测到 .env 文件,出于安全考虑,不会默认打包 .env 文件。请在部署时手动配置环境变量。');
|
|
@@ -100,14 +104,9 @@ if [ ! -f .env ]; then
|
|
|
100
104
|
echo "警告: 未找到 .env 文件,将使用默认配置或报错。请先复制 .env.example 为 .env 并修改配置。"
|
|
101
105
|
fi
|
|
102
106
|
|
|
103
|
-
#
|
|
104
|
-
#
|
|
105
|
-
|
|
106
|
-
# 这里的难点是 compose file 里写的是 build: .
|
|
107
|
-
# 离线环境没法 build。
|
|
108
|
-
# 解决方案:生成一个专门的 docker-compose.offline.yml
|
|
109
|
-
|
|
110
|
-
cat > docker-compose.override.yml <<EOF
|
|
107
|
+
# 启动服务 (强制使用离线镜像)
|
|
108
|
+
# 我们需要覆盖 build 配置,强制使用 image
|
|
109
|
+
cat > docker-compose.offline.yml <<EOF
|
|
111
110
|
services:
|
|
112
111
|
api:
|
|
113
112
|
build: !reset
|
|
@@ -117,7 +116,21 @@ services:
|
|
|
117
116
|
image: nodebbs-web:latest
|
|
118
117
|
EOF
|
|
119
118
|
|
|
120
|
-
|
|
119
|
+
# 注意:这里假设第二个文件总是 docker-compose.prod.yml
|
|
120
|
+
# 如果没有 override,命令可能会报错吗?
|
|
121
|
+
# 我们在 pack 的时候,只要 files > 1,就生成了 docker-compose.prod.yml
|
|
122
|
+
# 如果只有 base,install.sh 里的 -f docker-compose.prod.yml 会报错。
|
|
123
|
+
# 应该动态生成 install.sh 或者在这里加判断。
|
|
124
|
+
# 简单起见,我们确保 docker-compose.prod.yml 存在 (即使只是空的内容或者仅包含 version?)
|
|
125
|
+
# 或者在 install.sh 里判断文件是否存在。
|
|
126
|
+
|
|
127
|
+
COMPOSE_Args="-f docker-compose.yml"
|
|
128
|
+
if [ -f docker-compose.prod.yml ]; then
|
|
129
|
+
COMPOSE_Args="$COMPOSE_Args -f docker-compose.prod.yml"
|
|
130
|
+
fi
|
|
131
|
+
COMPOSE_Args="$COMPOSE_Args -f docker-compose.offline.yml"
|
|
132
|
+
|
|
133
|
+
docker compose $COMPOSE_Args up -d
|
|
121
134
|
|
|
122
135
|
echo "部署完成!"
|
|
123
136
|
`;
|
|
@@ -138,7 +151,9 @@ echo "部署完成!"
|
|
|
138
151
|
}
|
|
139
152
|
finally {
|
|
140
153
|
// 清理临时目录
|
|
141
|
-
fs.
|
|
154
|
+
if (fs.existsSync(tmpDir)) {
|
|
155
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
156
|
+
}
|
|
142
157
|
}
|
|
143
158
|
}
|
|
144
159
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { Command } from '@oclif/core';
|
|
2
2
|
import { runCompose, getComposeFiles } from '../../utils/docker.js';
|
|
3
3
|
import { logger } from '../../utils/logger.js';
|
|
4
|
-
import { EnvFlag, selectEnvironment } from '../../utils/selection.js';
|
|
4
|
+
import { EnvFlag, selectEnvironment, getDeploymentMode } from '../../utils/selection.js';
|
|
5
|
+
import { initImageEnv } from '../../utils/env.js';
|
|
5
6
|
export default class Restart extends Command {
|
|
6
|
-
static description = '
|
|
7
|
+
static description = '重启服务';
|
|
7
8
|
static flags = {
|
|
8
9
|
env: EnvFlag,
|
|
9
10
|
};
|
|
@@ -13,12 +14,17 @@ export default class Restart extends Command {
|
|
|
13
14
|
const env = await selectEnvironment(flags.env);
|
|
14
15
|
const { files, isBuiltIn } = await getComposeFiles(env);
|
|
15
16
|
logger.info('正在重启服务 (使用现有镜像)...');
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
// 确保镜像配置存在
|
|
18
|
+
await initImageEnv();
|
|
19
|
+
const mode = await getDeploymentMode();
|
|
20
|
+
logger.info(`环境: ${env} (${mode === 'image' ? '镜像部署' : '源码部署'})`);
|
|
20
21
|
// 3. 不会执行构建 (除非镜像不存在)
|
|
21
|
-
|
|
22
|
+
// 在镜像模式下,我们需要确保不尝试构建 (哪怕镜像丢失,应该是 pull 而不是 build)
|
|
23
|
+
const args = ['up', '-d', '--force-recreate'];
|
|
24
|
+
if (mode === 'image') {
|
|
25
|
+
args.push('--no-build');
|
|
26
|
+
}
|
|
27
|
+
await runCompose(files, args, isBuiltIn);
|
|
22
28
|
logger.success('服务已重启');
|
|
23
29
|
}
|
|
24
30
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { Command, Flags } from '@oclif/core';
|
|
2
2
|
import { confirm } from '@inquirer/prompts';
|
|
3
3
|
import { checkDocker, runCompose, waitForHealth, execCompose, getComposeFiles } from '../../utils/docker.js';
|
|
4
|
-
import { initEnv, checkEnv } from '../../utils/env.js';
|
|
4
|
+
import { initEnv, checkEnv, initDockerIgnore, initImageEnv } from '../../utils/env.js';
|
|
5
5
|
import { logger } from '../../utils/logger.js';
|
|
6
6
|
import dotenv from 'dotenv';
|
|
7
|
-
import { EnvFlag, selectEnvironment } from '../../utils/selection.js';
|
|
7
|
+
import { EnvFlag, selectEnvironment, getDeploymentMode } from '../../utils/selection.js';
|
|
8
8
|
export default class Start extends Command {
|
|
9
|
-
static description = '
|
|
9
|
+
static description = '启动服务';
|
|
10
10
|
static flags = {
|
|
11
11
|
env: EnvFlag,
|
|
12
12
|
build: Flags.boolean({
|
|
@@ -49,6 +49,9 @@ export default class Start extends Command {
|
|
|
49
49
|
await checkDocker();
|
|
50
50
|
// initEnv 保证 .env 存在(或退出)
|
|
51
51
|
await initEnv();
|
|
52
|
+
// 确保 .dockerignore 存在
|
|
53
|
+
await initDockerIgnore();
|
|
54
|
+
await initImageEnv();
|
|
52
55
|
await checkEnv(env);
|
|
53
56
|
if (!flags.build) {
|
|
54
57
|
const continueDeploy = await confirm({
|
|
@@ -61,15 +64,31 @@ export default class Start extends Command {
|
|
|
61
64
|
}
|
|
62
65
|
}
|
|
63
66
|
// 4. 构建并启动服务
|
|
67
|
+
const mode = await getDeploymentMode();
|
|
64
68
|
if (flags.build) {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
69
|
+
if (mode === 'image') {
|
|
70
|
+
logger.warning('镜像部署模式下不支持重新构建,将尝试拉取最新镜像...');
|
|
71
|
+
await runCompose(composeFiles, ['pull'], isBuiltIn);
|
|
72
|
+
await runCompose(composeFiles, ['up', '-d'], isBuiltIn);
|
|
73
|
+
logger.success('服务已更新并启动');
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
logger.info('正在重新构建并启动服务...');
|
|
77
|
+
await runCompose(composeFiles, ['up', '-d', '--build'], isBuiltIn);
|
|
78
|
+
logger.success('服务已重新构建并启动');
|
|
79
|
+
}
|
|
68
80
|
}
|
|
69
81
|
else {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
82
|
+
if (mode === 'source') {
|
|
83
|
+
logger.info('正在构建 Docker 镜像...');
|
|
84
|
+
await runCompose(composeFiles, ['build', '--no-cache'], isBuiltIn);
|
|
85
|
+
logger.success('镜像构建完成');
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
logger.info('检测到镜像部署模式,跳过本地构建...');
|
|
89
|
+
// 可选:在这里也可以尝试 pull 一下,或者让 up -d 自动处理
|
|
90
|
+
// await runCompose(composeFiles, ['pull'], isBuiltIn)
|
|
91
|
+
}
|
|
73
92
|
logger.info('正在启动服务...');
|
|
74
93
|
await runCompose(composeFiles, ['up', '-d'], isBuiltIn);
|
|
75
94
|
logger.success('服务启动指令已发送');
|
|
@@ -98,7 +117,7 @@ export default class Start extends Command {
|
|
|
98
117
|
}
|
|
99
118
|
logger.header(flags.build ? '启动成功!' : 'NodeBBS 启动成功!');
|
|
100
119
|
// 6. 显示信息
|
|
101
|
-
logger.info(`环境: ${env}`);
|
|
120
|
+
logger.info(`环境: ${env} (${mode === 'image' ? '镜像部署' : '源码部署'})`);
|
|
102
121
|
const envConfig = dotenv.config().parsed || {};
|
|
103
122
|
const webPort = envConfig.WEB_PORT || '3100';
|
|
104
123
|
const apiPort = envConfig.API_PORT || '7100';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class Upgrade extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static flags: {
|
|
5
|
+
env: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
6
|
+
};
|
|
7
|
+
run(): Promise<void>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
import { runCompose, getComposeFiles } from '../../utils/docker.js';
|
|
3
|
+
import { logger } from '../../utils/logger.js';
|
|
4
|
+
import { EnvFlag, selectEnvironment, getDeploymentMode } from '../../utils/selection.js';
|
|
5
|
+
import { initImageEnv } from '../../utils/env.js';
|
|
6
|
+
import Start from '../start/index.js';
|
|
7
|
+
export default class Upgrade extends Command {
|
|
8
|
+
static description = '升级服务';
|
|
9
|
+
static flags = {
|
|
10
|
+
env: EnvFlag,
|
|
11
|
+
};
|
|
12
|
+
async run() {
|
|
13
|
+
const { flags } = await this.parse(Upgrade);
|
|
14
|
+
// 1. 检查部署模式
|
|
15
|
+
const mode = await getDeploymentMode();
|
|
16
|
+
if (mode === 'source') {
|
|
17
|
+
logger.info('当前为源码部署模式,执行重建...');
|
|
18
|
+
const args = ['--build'];
|
|
19
|
+
if (flags.env)
|
|
20
|
+
args.push('--env', flags.env);
|
|
21
|
+
await Start.run(args);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
// 2. 镜像模式:拉取更新并重启
|
|
25
|
+
logger.header('NodeBBS 服务升级');
|
|
26
|
+
// 选择环境 (通常需要与运行中一致,这里重新选择以防万一,或者可以复用 restart 的逻辑不传 env 则自选)
|
|
27
|
+
//为了简单,我们遵循 restart 的模式
|
|
28
|
+
const env = await selectEnvironment(flags.env);
|
|
29
|
+
// 确保镜像配置存在
|
|
30
|
+
await initImageEnv();
|
|
31
|
+
const { files, isBuiltIn } = await getComposeFiles(env);
|
|
32
|
+
logger.info('正在拉取最新镜像...');
|
|
33
|
+
await runCompose(files, ['pull'], isBuiltIn);
|
|
34
|
+
logger.success('镜像已更新');
|
|
35
|
+
logger.info('正在重新启动服务...');
|
|
36
|
+
await runCompose(files, ['up', '-d'], isBuiltIn);
|
|
37
|
+
logger.success('服务已升级并启动');
|
|
38
|
+
}
|
|
39
|
+
}
|
package/dist/interactive.js
CHANGED
|
@@ -29,7 +29,7 @@ export async function runInteractive(root) {
|
|
|
29
29
|
}
|
|
30
30
|
await navigate(tree, [], config);
|
|
31
31
|
}
|
|
32
|
-
const GLOBAL_PRIORITY = ['start', 'stop', 'restart', '
|
|
32
|
+
const GLOBAL_PRIORITY = ['start', 'stop', 'restart', 'upgrade', 'status', 'logs', 'shell', 'db', 'pack'];
|
|
33
33
|
const SCOPED_PRIORITIES = {
|
|
34
34
|
'logs': ['all', 'web', 'api', 'db', 'redis'],
|
|
35
35
|
};
|
|
@@ -3,17 +3,10 @@
|
|
|
3
3
|
# 使用方式: docker compose -f docker-compose.yml -f docker-compose.lowmem.yml up -d
|
|
4
4
|
|
|
5
5
|
services:
|
|
6
|
-
# PostgreSQL
|
|
6
|
+
# PostgreSQL - 低内存优化
|
|
7
7
|
postgres:
|
|
8
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
|
|
9
|
+
ports: []
|
|
17
10
|
deploy:
|
|
18
11
|
resources:
|
|
19
12
|
limits:
|
|
@@ -28,24 +21,19 @@ services:
|
|
|
28
21
|
max-size: "5m"
|
|
29
22
|
max-file: "2"
|
|
30
23
|
|
|
31
|
-
# Redis
|
|
24
|
+
# Redis - 低内存优化
|
|
32
25
|
redis:
|
|
33
26
|
restart: always
|
|
27
|
+
ports: []
|
|
34
28
|
command: >
|
|
35
29
|
redis-server
|
|
36
|
-
--requirepass ${REDIS_PASSWORD}
|
|
30
|
+
--requirepass ${REDIS_PASSWORD:-redis_password}
|
|
37
31
|
--appendonly yes
|
|
38
32
|
--appendfsync everysec
|
|
39
33
|
--maxmemory 128mb
|
|
40
34
|
--maxmemory-policy allkeys-lru
|
|
41
35
|
--save 900 1
|
|
42
36
|
--save 300 10
|
|
43
|
-
ports: [] # 不暴露端口到主机
|
|
44
|
-
healthcheck:
|
|
45
|
-
interval: 30s
|
|
46
|
-
timeout: 10s
|
|
47
|
-
retries: 5
|
|
48
|
-
start_period: 20s
|
|
49
37
|
deploy:
|
|
50
38
|
resources:
|
|
51
39
|
limits:
|
|
@@ -60,21 +48,12 @@ services:
|
|
|
60
48
|
max-size: "5m"
|
|
61
49
|
max-file: "2"
|
|
62
50
|
|
|
63
|
-
# API
|
|
51
|
+
# API - 低内存优化
|
|
64
52
|
api:
|
|
65
53
|
restart: always
|
|
66
54
|
environment:
|
|
67
|
-
|
|
68
|
-
JWT_SECRET: ${JWT_SECRET}
|
|
69
|
-
CORS_ORIGIN: ${CORS_ORIGIN}
|
|
70
|
-
APP_URL: ${APP_URL}
|
|
71
|
-
# Node.js 内存限制
|
|
55
|
+
# 严格限制 Node.js 内存
|
|
72
56
|
NODE_OPTIONS: "--max-old-space-size=384"
|
|
73
|
-
volumes:
|
|
74
|
-
- api_uploads:/app/apps/api/uploads
|
|
75
|
-
healthcheck:
|
|
76
|
-
start_period: 90s # 低内存环境启动更慢
|
|
77
|
-
interval: 60s
|
|
78
57
|
deploy:
|
|
79
58
|
resources:
|
|
80
59
|
limits:
|
|
@@ -89,21 +68,12 @@ services:
|
|
|
89
68
|
max-size: "10m"
|
|
90
69
|
max-file: "3"
|
|
91
70
|
|
|
92
|
-
# Web
|
|
71
|
+
# Web - 低内存优化
|
|
93
72
|
web:
|
|
94
73
|
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
74
|
environment:
|
|
100
|
-
|
|
101
|
-
NEXT_PUBLIC_APP_URL: ${NEXT_PUBLIC_APP_URL}
|
|
102
|
-
# Node.js 内存限制
|
|
75
|
+
# 严格限制 Node.js 内存
|
|
103
76
|
NODE_OPTIONS: "--max-old-space-size=384"
|
|
104
|
-
healthcheck:
|
|
105
|
-
start_period: 90s # 低内存环境启动更慢
|
|
106
|
-
interval: 60s
|
|
107
77
|
deploy:
|
|
108
78
|
resources:
|
|
109
79
|
limits:
|
|
@@ -118,9 +88,4 @@ services:
|
|
|
118
88
|
max-size: "10m"
|
|
119
89
|
max-file: "3"
|
|
120
90
|
|
|
121
|
-
|
|
122
|
-
networks:
|
|
123
|
-
nodebbs-network:
|
|
124
|
-
ipam:
|
|
125
|
-
config:
|
|
126
|
-
- subnet: 172.28.0.0/16
|
|
91
|
+
|
|
@@ -3,17 +3,10 @@
|
|
|
3
3
|
# 本文件只包含与 docker-compose.yml 的差异部分
|
|
4
4
|
|
|
5
5
|
services:
|
|
6
|
-
# PostgreSQL
|
|
6
|
+
# PostgreSQL - 生产环境安全与资源优化
|
|
7
7
|
postgres:
|
|
8
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
|
|
9
|
+
ports: [] # 安全:不暴露端口到主机
|
|
17
10
|
deploy:
|
|
18
11
|
resources:
|
|
19
12
|
limits:
|
|
@@ -28,9 +21,10 @@ services:
|
|
|
28
21
|
max-size: "10m"
|
|
29
22
|
max-file: "3"
|
|
30
23
|
|
|
31
|
-
# Redis
|
|
24
|
+
# Redis - 生产环境安全与资源优化
|
|
32
25
|
redis:
|
|
33
26
|
restart: always
|
|
27
|
+
ports: [] # 安全:不暴露端口到主机
|
|
34
28
|
command: >
|
|
35
29
|
redis-server
|
|
36
30
|
--requirepass ${REDIS_PASSWORD}
|
|
@@ -38,12 +32,6 @@ services:
|
|
|
38
32
|
--appendfsync everysec
|
|
39
33
|
--maxmemory 256mb
|
|
40
34
|
--maxmemory-policy allkeys-lru
|
|
41
|
-
ports: [] # 生产环境不暴露端口到主机
|
|
42
|
-
healthcheck:
|
|
43
|
-
interval: 30s
|
|
44
|
-
timeout: 10s
|
|
45
|
-
retries: 5
|
|
46
|
-
start_period: 20s
|
|
47
35
|
deploy:
|
|
48
36
|
resources:
|
|
49
37
|
limits:
|
|
@@ -58,19 +46,12 @@ services:
|
|
|
58
46
|
max-size: "10m"
|
|
59
47
|
max-file: "3"
|
|
60
48
|
|
|
61
|
-
# API
|
|
49
|
+
# API - 生产环境资源优化
|
|
62
50
|
api:
|
|
63
51
|
restart: always
|
|
64
52
|
environment:
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
CORS_ORIGIN: ${CORS_ORIGIN} # 生产环境必须明确设置
|
|
68
|
-
APP_URL: ${APP_URL} # 生产环境实际域名
|
|
69
|
-
NODE_OPTIONS: "--max-old-space-size=512" # 限制 Node.js 内存使用
|
|
70
|
-
volumes:
|
|
71
|
-
- api_uploads:/app/apps/api/uploads # 生产环境不挂载源代码
|
|
72
|
-
healthcheck:
|
|
73
|
-
start_period: 60s
|
|
53
|
+
# 生产环境特有配置
|
|
54
|
+
NODE_OPTIONS: "--max-old-space-size=512"
|
|
74
55
|
deploy:
|
|
75
56
|
resources:
|
|
76
57
|
limits:
|
|
@@ -85,19 +66,12 @@ services:
|
|
|
85
66
|
max-size: "20m"
|
|
86
67
|
max-file: "5"
|
|
87
68
|
|
|
88
|
-
# Web
|
|
69
|
+
# Web - 生产环境资源优化
|
|
89
70
|
web:
|
|
90
71
|
restart: always
|
|
91
|
-
build:
|
|
92
|
-
args:
|
|
93
|
-
NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL}
|
|
94
|
-
NEXT_PUBLIC_APP_URL: ${NEXT_PUBLIC_APP_URL}
|
|
95
72
|
environment:
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
NODE_OPTIONS: "--max-old-space-size=512" # 限制 Node.js 内存使用
|
|
99
|
-
healthcheck:
|
|
100
|
-
start_period: 60s
|
|
73
|
+
# 生产环境特有配置
|
|
74
|
+
NODE_OPTIONS: "--max-old-space-size=512"
|
|
101
75
|
deploy:
|
|
102
76
|
resources:
|
|
103
77
|
limits:
|
|
@@ -112,9 +86,4 @@ services:
|
|
|
112
86
|
max-size: "20m"
|
|
113
87
|
max-file: "5"
|
|
114
88
|
|
|
115
|
-
|
|
116
|
-
networks:
|
|
117
|
-
nodebbs-network:
|
|
118
|
-
ipam:
|
|
119
|
-
config:
|
|
120
|
-
- subnet: 172.28.0.0/16
|
|
89
|
+
|
|
@@ -11,7 +11,7 @@ services:
|
|
|
11
11
|
TZ: Asia/Shanghai
|
|
12
12
|
volumes:
|
|
13
13
|
- postgres_data:/var/lib/postgresql/data
|
|
14
|
-
|
|
14
|
+
|
|
15
15
|
ports:
|
|
16
16
|
- "${POSTGRES_PORT:-5432}:5432"
|
|
17
17
|
healthcheck:
|
|
@@ -45,8 +45,9 @@ services:
|
|
|
45
45
|
# API 服务
|
|
46
46
|
api:
|
|
47
47
|
build:
|
|
48
|
-
context:
|
|
48
|
+
context: ../../
|
|
49
49
|
dockerfile: apps/api/Dockerfile
|
|
50
|
+
image: ${API_IMAGE:-nodebbs-api:local}
|
|
50
51
|
container_name: nodebbs-api
|
|
51
52
|
restart: unless-stopped
|
|
52
53
|
environment:
|
|
@@ -64,7 +65,7 @@ services:
|
|
|
64
65
|
TZ: Asia/Shanghai
|
|
65
66
|
volumes:
|
|
66
67
|
- api_uploads:/app/apps/api/uploads
|
|
67
|
-
- ./apps/api/src:/app/apps/api/src:ro
|
|
68
|
+
# - ./apps/api/src:/app/apps/api/src:ro
|
|
68
69
|
ports:
|
|
69
70
|
- "${API_PORT:-7100}:7100"
|
|
70
71
|
depends_on:
|
|
@@ -84,19 +85,16 @@ services:
|
|
|
84
85
|
# Web 前端服务
|
|
85
86
|
web:
|
|
86
87
|
build:
|
|
87
|
-
context:
|
|
88
|
+
context: ../../
|
|
88
89
|
dockerfile: apps/web/Dockerfile
|
|
89
|
-
|
|
90
|
-
NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-http://localhost:7100}
|
|
91
|
-
NEXT_PUBLIC_APP_URL: ${NEXT_PUBLIC_APP_URL:-http://localhost:3100}
|
|
90
|
+
image: ${WEB_IMAGE:-nodebbs-web:local}
|
|
92
91
|
container_name: nodebbs-web
|
|
93
92
|
restart: unless-stopped
|
|
94
93
|
environment:
|
|
95
94
|
NODE_ENV: production
|
|
96
95
|
APP_NAME: ${APP_NAME:-nodebbs}
|
|
97
96
|
PORT: 3100
|
|
98
|
-
|
|
99
|
-
NEXT_PUBLIC_APP_URL: ${NEXT_PUBLIC_APP_URL:-http://localhost:3100}
|
|
97
|
+
SERVER_API_URL: ${SERVER_API_URL:-http://api:7100}
|
|
100
98
|
TZ: Asia/Shanghai
|
|
101
99
|
ports:
|
|
102
100
|
- "${WEB_PORT:-3100}:3100"
|