본 문서는 DEV_README.md (v8)를 기반으로, 실제 구현 및 배포에 필요한 10가지 세부 항목(CI/CD, 큐 파라미터, 로깅, Mocking 등)을 정의합니다.
DEV_README.md (v8)의 ConfigModule을 지원하기 위한 상세 .env 키 목록입니다.
# -——————————–
# NestJS & Server
# -——————————–
NODE_ENV=development
SERVER_PORT=3000
# -——————————–
# Auth & Security (A-120, 7번 항목)
# -——————————–
# 콤마(,)로 구분된 복수의 키를 지원 (키 회전용)
GLOBAL_SECRET_KEY=primary_key,secondary_key
# Throttler (Rate Limit) - 1분에 60개 요청
THROTTLE_TTL=60
THROTTLE_LIMIT=60
# -——————————–
# Databases (P1-1, P1-3)
# -——————————–
DB_URI=mongodb+srv://…
REDIS_HOST=localhost
REDIS_PORT=6379
# -——————————–
# AWS Credentials (P1-2, M-050)
# -——————————–
# (권장) EC2 Instance Role을 사용하면 아래 2개는 필요 없음. 로컬 개발용.
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_REGION=ap-northeast-2
S3_BUCKET_NAME=miji-nft-assets-dev
# -——————————–
# Blockchain & Secrets (P1-2, B-100)
# -——————————–
# (핵심) 서버 지갑 PK가 저장된 Secrets Manager의 ARN
PK_SECRET_ARN=arn:aws:secretsmanager:ap-northeast-2:….
# (핵심) PK 캐시 TTL (초) (M1 제안)
PK_CACHE_TTL_SECONDS=300
# (신규) 로컬 개발 시 Mock PK (2번 항목 참조)
LOCAL_DEV_PK=0x…
# -——————————–
# Chain RPC Endpoints (B-100)
# -——————————–
CHAIN_RPC_URL_43114=[https://api.avax.network/ext/bc/C/rpc](https://api.avax.network/ext/bc/C/rpc)
CHAIN_RPC_URL_137=[https://polygon-rpc.com](https://polygon-rpc.com)
# -——————————–
# Webhooks (H-200)
# -——————————–
WEBHOOK_URL_DEFAULT=[https://api.miji.service.com/v1/webhook/nft](https://api.miji.service.com/v1/webhook/nft)
WEBHOOK_SECRET=whsec_…
PLAN.md (M1)에 따라 PK는 5분(300초)간 인메모리 캐싱합니다.
저장 포맷: PK_SECRET_ARN이 가리키는 Secret은 다음 JSON 구조를 따릅니다.
{
“default_wallet_pk”: “0xabc123…”
}
PLAN.md (v8)의 3개 큐에 대해, concurrency (동시 처리 수) 및 timeout (작업 시간 초과)을 명시합니다. (M2 제안)
| 큐 이름 | PLAN.md ID | Concurrency (동시 처리) | Timeout (ms) | Job 옵션 (기본값) |
|---|---|---|---|---|
| minting-queue | (T-110) | 5 (노드 과부하 방지) | 120,000 (2분) | attempts: 3, backoff: { type: ‘exponential’, delay: 5000 } |
| deployment-queue | (C-130) | 1 (배포는 순차 처리) | 300,000 (5분) | attempts: 2, backoff: { type: ‘exponential’, delay: 10000 } |
| webhook-queue | (H-200) | 10 (웹훅은 다발적 전송) | 5,000 (5초) | attempts: 5, backoff: { type: ‘exponential’, delay: 10000 } |
사용자 시나리오(GitHub Actions -> ECR -> CodeDeploy -> EC2)를 반영한 상세 워크플로우입니다.
server/Dockerfile (멀티 스테이지/아티팩트 포함)
# -——————————–
# Stage 1: Builder
# -——————————–
FROM node:18-alpine AS builder
WORKDIR /app
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
COPY server/package.json ./server/
COPY contracts/package.json ./contracts/
RUN npm install -g pnpm
RUN pnpm install --frozen-lockfile
COPY . .
RUN pnpm --filter contracts build
RUN pnpm --filter server build
# -——————————–
# Stage 2: Final Runtime
# -——————————–
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/server/dist ./dist
COPY --from=builder /app/contracts/artifacts ./artifacts
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/server/node_modules ./server/node_modules
CMD [“node”, “dist/main.js”]
main 브랜치 푸시 시, Docker 이미지를 빌드/푸시하고 CodeDeploy 배포를 트리거합니다.
name: Deploy to EC2 (Dev)
on:
push:
branches:
- main # (Dev 배포용) 또는 ‘develop’ 브랜치
jobs:
# 1. Docker 이미지 빌드 및 ECR 푸시
build-and-push:
name: Build and Push to ECR
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
\- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: $
aws-secret-access-key: $
aws-region: $
\- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2
\- name: Set up QEMU (for ARM build)
uses: docker/setup-qemu-action@v3
\- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
\- name: Build and push (ARM64)
id: build-image
uses: docker/build-push-action@v5
with:
context: .
file: server/Dockerfile
push: true
tags: $/$:latest
platforms: linux/arm64 \# (B1) ARM64 전용 빌드
# 2. AWS CodeDeploy 실행
trigger-deploy:
name: Trigger AWS CodeDeploy
runs-on: ubuntu-latest
needs: build-and-push # 빌드 잡이 성공해야 실행
steps:
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
# (자격 증명 동일)
\- name: Trigger CodeDeploy
run: |
aws deploy create-deployment \\
\--application-name Miji-NFT-Server-App \\
\--deployment-group-name Miji-NFT-Server-Dev-Group \\
\--deployment-config-name CodeDeployDefault.OneAtATime \\
\--description "Deploy from GitHub Actions" \\
\--revision '{"revisionType": "AppSpecContent", "appSpecContent": {"content": "...", "sha256": "..."}}'
\# (참고: CodeDeploy 설정에 따라 ECR/S3 리비전을 지정해야 함)
EC2 인스턴스에 codedeploy-agent가 설치되어 있어야 하며, 루트에 appspec.yml 파일이 필요합니다.
/appspec.yml
version: 0.0
os: linux
files:
- source: /docker-compose.yml # (6번 항목)
destination: /home/ec2-user/miji-nft-server/
hooks:
ApplicationStop:
- location: scripts/stop_server.sh
timeout: 30
runas: ec2-user
BeforeInstall:
- location: scripts/pull_image.sh
timeout: 300
runas: ec2-user
ApplicationStart:
- location: scripts/start_server.sh
timeout: 60
runas: ec2-user
/scripts/pull_image.sh (CodeDeploy가 ECR에서 새 이미지를 받도록 함)
#!/bin/bash
AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
ECR_REGISTRY=${AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-2.amazonaws.com
IMAGE_NAME=miji-nft-repository-name # (GitHub 변수와 일치)
aws ecr get-login-password --region ap-northeast-2 | docker login --username AWS --password-stdin $ECR_REGISTRY
docker pull $ECR_REGISTRY/$IMAGE_NAME:latest
/scripts/start_server.sh (Docker Compose 실행)
#!/bin/bash
cd /home/ec2-user/miji-nft-server/
# .env 파일은 CodeDeploy 외부에서 관리(e.g., S3에서 가져오기)되어야 함
docker-compose up -d --no-build
(DEV_README.md (v8) 원안을 따름)
서버는 **동적 배포 API(C-130)**를 제공합니다. CI/CD(Dockerfile) 시 contracts/의 ABI/Bytecode를 서버 이미지에 미리 구워넣고, ContractsProcessor가 이 아티팩트를 읽어 deployment-queue를 통해 비동기로 배포합니다.
(제안대로 확정)
server/src/app.module.ts (설정 예시):
import { Module } from ‘@nestjs/common’;
import { LoggerModule } from ‘nestjs-pino’;
@Module({
imports: [
LoggerModule.forRootAsync({
useFactory: () => ({
pinoHttp: {
// (M3) Production에서는 JSON, Dev에서는 예쁘게
transport: process.env.NODE_ENV !== ‘production’
? { target: ‘pino-pretty’ }
: undefined,
// (M3) 타임스탬프, 레벨, 컨텍스트 포함 (기본값)
level: process.env.NODE_ENV !== ‘production’ ? ‘debug’ : ‘info’,
},
}),
}),
// … (기타 모듈)
],
})
export class AppModule {}