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.
@@ -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
- // 这里我们需要获取当前的 docker-compose.ymlprod 配置
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
- await execa('docker', ['compose', 'build'], { stdio: 'inherit' });
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
- const images = [
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
- // 读取 docker-compose.yml 并移除开发环境的源码挂载
65
- // 这是一个关键步骤,因为在离线/生产环境中,我们不应该挂载 ./apps/xxx/src,
66
- // 否则会覆盖掉镜像内构建好的代码,导致 "Cannot find module" 错误。
67
- let composeContent = fs.readFileSync('docker-compose.yml', 'utf-8');
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.includes('./apps/api/src') || line.includes('./apps/web/src')) {
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
- if (fs.existsSync('docker-compose.prod.yml')) {
78
- fs.copyFileSync('docker-compose.prod.yml', path.join(tmpDir, 'docker-compose.prod.yml'));
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
- # 修改 image 名称以匹配 load 进来的 tag (如果 compose 文件里没写 image,它会尝试 build)
104
- # 为了避免 build,我们需要修改 docker-compose.yml 或者使用环境变量 override
105
- # 最简单的办法:我们在 install 脚本里,强制用 docker run 或者修改 compose 文件?
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
- docker compose -f docker-compose.yml -f docker-compose.prod.yml -f docker-compose.override.yml up -d
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.rmSync(tmpDir, { recursive: true, force: true });
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 = '重启所有服务 (up --force-recreate)';
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
- logger.info('环境: ' + env);
17
- // 使用 up -d --force-recreate 可以:
18
- // 1. 如果服务不存在,则创建并启动
19
- // 2. 如果服务已存在,则强制重启
17
+ // 确保镜像配置存在
18
+ await initImageEnv();
19
+ const mode = await getDeploymentMode();
20
+ logger.info(`环境: ${env} (${mode === 'image' ? '镜像部署' : '源码部署'})`);
20
21
  // 3. 不会执行构建 (除非镜像不存在)
21
- await runCompose(files, ['up', '-d', '--force-recreate'], isBuiltIn);
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
- logger.info('正在重新构建并启动服务...');
66
- await runCompose(composeFiles, ['up', '-d', '--build'], isBuiltIn);
67
- logger.success('服务已重新构建并启动');
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
- logger.info('正在构建 Docker 镜像...');
71
- await runCompose(composeFiles, ['build', '--no-cache'], isBuiltIn);
72
- logger.success('镜像构建完成');
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
+ }
@@ -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', 'rebuild', 'status', 'logs', 'shell', 'db', 'pack'];
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
  };
@@ -0,0 +1,10 @@
1
+ services:
2
+ api:
3
+ build:
4
+ context: .
5
+ dockerfile: apps/api/Dockerfile
6
+
7
+ web:
8
+ build:
9
+ context: .
10
+ dockerfile: apps/web/Dockerfile
@@ -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
- USER_CACHE_TTL: ${USER_CACHE_TTL:-300}
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
- NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL}
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
- USER_CACHE_TTL: ${USER_CACHE_TTL:-300} # 生产环境缓存时间更长
66
- JWT_SECRET: ${JWT_SECRET} # 生产环境必须设置
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
- NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL}
97
- NEXT_PUBLIC_APP_URL: ${NEXT_PUBLIC_APP_URL}
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
- - ${INIT_DB_PATH:-./scripts/init-db.sql}:/docker-entrypoint-initdb.d/init.sql:ro
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: . # 从 monorepo 根目录构建(turbo prune 需要完整的 workspace)
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: . # 从 monorepo 根目录构建(turbo prune 需要完整的 workspace)
88
+ context: ../../
88
89
  dockerfile: apps/web/Dockerfile
89
- args:
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
- NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-http://localhost:7100}
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"