From d4a7118d7ebd69272fac0bae9876f126da7d0b8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=85=B8=20=EA=B8=B0=20=ED=9B=88?= Date: Fri, 3 Jul 2026 18:48:33 +0900 Subject: [PATCH 1/3] feat(server): add lightweight /health endpoint for ALB health checks --- apps/server/src/app.module.ts | 3 ++- apps/server/src/health/health.controller.ts | 12 ++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 apps/server/src/health/health.controller.ts diff --git a/apps/server/src/app.module.ts b/apps/server/src/app.module.ts index ca0ef27..9228e17 100644 --- a/apps/server/src/app.module.ts +++ b/apps/server/src/app.module.ts @@ -11,6 +11,7 @@ import { BookmarkModule } from './bookmark/bookmark.module'; import { PrismaExceptionFilter } from './common/filters/prisma-exception.filter'; import { RedisModule } from './common/redis/redis.module'; import { validateEnv } from './config/env.validation'; +import { HealthController } from './health/health.controller'; import { PrismaModule } from './prisma/prisma.module'; import { SessionModule } from './session/session.module'; import { UserModule } from './user/user.module'; @@ -33,7 +34,7 @@ import { UserModule } from './user/user.module'; ]), ScheduleModule.forRoot(), ], - controllers: [AppController], + controllers: [AppController, HealthController], providers: [ AppService, { diff --git a/apps/server/src/health/health.controller.ts b/apps/server/src/health/health.controller.ts new file mode 100644 index 0000000..a7204c0 --- /dev/null +++ b/apps/server/src/health/health.controller.ts @@ -0,0 +1,12 @@ +import { Controller, Get } from '@nestjs/common'; +import { SkipThrottle } from '@nestjs/throttler'; + +// ALB 타깃 그룹 헬스체크용 경량 liveness 엔드포인트. +@SkipThrottle() +@Controller('health') +export class HealthController { + @Get() + check() { + return { status: 'ok', timestamp: new Date().toISOString() }; + } +} From 4d09390daf4056810c23e17453779d44ae843013 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=85=B8=20=EA=B8=B0=20=ED=9B=88?= Date: Fri, 3 Jul 2026 18:48:54 +0900 Subject: [PATCH 2/3] chore(server): bundle with webpack to inline workspace packages --- apps/server/webpack.config.js | 29 +++++++++++++++++++++++++++++ pnpm-lock.yaml | 3 +++ 2 files changed, 32 insertions(+) create mode 100644 apps/server/webpack.config.js diff --git a/apps/server/webpack.config.js b/apps/server/webpack.config.js new file mode 100644 index 0000000..3367958 --- /dev/null +++ b/apps/server/webpack.config.js @@ -0,0 +1,29 @@ +const nodeExternals = require('webpack-node-externals'); + +/** + * `nest build --webpack` 전용 설정. + * + * 워크스페이스 패키지(@dookmark/*)는 컴파일되지 않은 raw TypeScript를 + * main 필드로 내보낸다(예: @dookmark/analytics의 DASHBOARD_PERIODS 같은 + * 런타임 값). + * + * tsc만 쓰면 dist에 require("@dookmark/analytics")가 남아 + * 런타임에 .ts를 로드하려다 크래시한다. + * + * => webpack으로 @dookmark/* 소스만 번들에 인라인하고, 나머지 실제 + * npm 의존성(@nestjs/*, prisma, socket.io, bcrypt 등)은 externalize해 + * node_modules에서 그대로 require하게 한다. + * ts-loader가 실제 tsc로 + * 컴파일하므로 NestJS 데코레이터 메타데이터(emitDecoratorMetadata)도 보존된다. + * + * dev(nest start)는 이 설정을 쓰지 않으므로 개발 경험은 그대로다. + */ +module.exports = (options) => ({ + ...options, + externals: [ + nodeExternals({ + // @dookmark/* 는 externalize하지 말고 번들에 포함 + allowlist: [/^@dookmark\//], + }), + ], +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 60abded..ac158a4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -274,6 +274,9 @@ importers: typescript: specifier: ^5.7.3 version: 5.9.3 + webpack-node-externals: + specifier: ^3.0.0 + version: 3.0.0 apps/web: dependencies: From 4b3211eaa7b52c662e8da5efe84c928f8c24d4cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=85=B8=20=EA=B8=B0=20=ED=9B=88?= Date: Fri, 3 Jul 2026 18:58:55 +0900 Subject: [PATCH 3/3] chore(server): add arm64 Dockerfile for containerized deployment --- apps/server/Dockerfile | 40 ++++++++++++++++++++++++++++++++++++++++ apps/server/package.json | 5 +++-- 2 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 apps/server/Dockerfile diff --git a/apps/server/Dockerfile b/apps/server/Dockerfile new file mode 100644 index 0000000..249fd0a --- /dev/null +++ b/apps/server/Dockerfile @@ -0,0 +1,40 @@ +# syntax=docker/dockerfile:1 + +# +# 빌드 컨텍스트 = 저장소 루트 (packages/* 와 pnpm-lock.yaml 접근 필요): +# docker buildx build --platform linux/arm64 -f apps/server/Dockerfile -t . +# +# EC2가 Graviton(ARM)이므로 반드시 linux/arm64 로 빌드한다. +# x86 로 빌드하면 인스턴스에서 실행되지 않는다. + +FROM node:22.13.0-slim AS base +# Prisma 엔진이 요구하는 OpenSSL, 아웃바운드 HTTPS(SMTP/OAuth/ECR)용 CA 인증서 +RUN apt-get update \ + && apt-get install -y --no-install-recommends openssl ca-certificates \ + && rm -rf /var/lib/apt/lists/* +# corepack 번들 버전의 서명키가 오래돼 최신 pnpm 검증에 실패하므로 npm 으로 직접 설치. +RUN npm install -g pnpm@10.15.0 +WORKDIR /app + +# 워크스페이스 전체 복사 (.dockerignore 가 node_modules/dist/.env 등을 제외). +# frozen-lockfile 검증을 위해 모든 워크스페이스 package.json 이 필요하므로 전체 복사한다. +COPY . . + +# server 서브트리 의존성만 설치 (web/extension 미포함). 락파일 고정. +RUN pnpm install --frozen-lockfile --filter server... + +# Prisma 클라이언트 생성. generate 는 DB 연결이 불필요하지만 +# prisma.config.ts 가 process.env.DATABASE_URL 을 읽으므로 더미 값을 준다. +RUN DATABASE_URL="postgresql://user:pass@localhost:5432/db?schema=public" \ + pnpm --filter server exec prisma generate + +# webpack 번들 빌드 → apps/server/dist/main.js (단일 파일, @dookmark/* 인라인) +RUN pnpm --filter server build + +ENV NODE_ENV=production +ENV PORT=3001 +WORKDIR /app/apps/server +EXPOSE 3001 + +# 컨테이너는 평문 HTTP(:3001)만 처리한다. TLS 는 앞단 ALB 가 종단. +CMD ["node", "dist/main.js"] diff --git a/apps/server/package.json b/apps/server/package.json index 75fda27..b18c408 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -6,7 +6,7 @@ "private": true, "license": "UNLICENSED", "scripts": { - "build": "nest build", + "build": "nest build --webpack --webpackPath webpack.config.js", "dev": "nest start --watch", "start": "nest start", "start:dev": "nest start --watch", @@ -83,7 +83,8 @@ "ts-loader": "^9.5.2", "ts-node": "^10.9.2", "tsconfig-paths": "^4.2.0", - "typescript": "^5.7.3" + "typescript": "^5.7.3", + "webpack-node-externals": "^3.0.0" }, "jest": { "moduleFileExtensions": [