docs: 템플릿 규칙 정비 및 환경변수 예제·README 추가

- CLAUDE.md: 모노레포 개요·표준 응답 포맷·에러 코드 맵(구현 일치)·환경변수 위임 구조(SSOT) 정리
- nestjs/vue3/flutter 규칙을 실제 구현(클래스명, env키, 디렉터리)과 정합화
- 루트 .env 단일 진실 공급원 구조에 맞춰 docker-compose 변수명 정정(BACKEND_API_URL, DB/REDIS_DATA_DIR)
- backend/frontend/flutter 예제 env 추가(루트 위임 반영, 서비스 고유 값만 작성)
- README.md 신규 작성, command.md 범용화, .gitignore에 *.example 재포함 규칙 추가

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-15 11:14:42 +09:00
parent 543d7b85fa
commit f5a18a52f4
15 changed files with 423 additions and 51 deletions
+10 -8
View File
@@ -24,8 +24,8 @@ lib/
├── core/
│ ├── constants/ ← 상수 (색상, 크기, 텍스트 등)
│ ├── theme/ ← 앱 테마 정의
│ ├── network/ ← Dio 인스턴스, 인터셉터
│ ├── storage/ ← flutter_secure_storage 래퍼
│ ├── api/ ← Dio 인스턴스(dio_client), 에러 사전(error_lexicon), 인터셉터
│ ├── storage/ ← flutter_secure_storage 래퍼 (선택)
│ └── utils/ ← 순수 유틸 함수
├── features/
│ └── user/
@@ -136,14 +136,16 @@ class UserWidget extends ConsumerWidget {
## API 통신 (Dio + 인터셉터)
```dart
// core/network/dio_client.dart
// core/api/dio_client.dart
class DioClient {
late final Dio _dio;
DioClient() {
_dio = Dio(BaseOptions(baseUrl: Env.apiUrl));
// flutter_dotenv 로 .env 로드, 미설정 시 로컬 기본값
final baseUrl = dotenv.maybeGet('API_URL') ?? 'http://localhost:3000/api';
_dio = Dio(BaseOptions(baseUrl: baseUrl));
_dio.interceptors.add(AuthInterceptor()); // 토큰 자동 첨부
_dio.interceptors.add(ErrorInterceptor()); // 에러 코드 처리
_dio.interceptors.add(ErrorInterceptor()); // 에러 코드 처리 (error_lexicon 참조)
}
}
@@ -164,11 +166,11 @@ class ErrorInterceptor extends Interceptor {
// flutter_secure_storage 사용
const storage = FlutterSecureStorage();
// 저장
await storage.write(key: 'access_token', value: token);
// 저장 (키 이름은 dio_client 인터셉터와 통일: jwt_token)
await storage.write(key: 'jwt_token', value: token);
// 읽기
final token = await storage.read(key: 'access_token');
final token = await storage.read(key: 'jwt_token');
// 삭제 (로그아웃)
await storage.deleteAll();
+7 -6
View File
@@ -74,21 +74,22 @@ export class CreateUserDto {
## 표준 응답 인터셉터
```typescript
// common/interceptors/response.interceptor.ts
// common/interceptors/transform.interceptor.ts
@Injectable()
export class ResponseInterceptor implements NestInterceptor {
export class TransformInterceptor<T> implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
map((data) => ({
success: true,
data,
data: data ?? null,
})),
);
}
}
```
- `main.ts`에서 전역 등록: `app.useGlobalInterceptors(new ResponseInterceptor())`
- `main.ts`에서 전역 등록: `app.useGlobalInterceptors(new TransformInterceptor())`
- 실패 응답(`success: false`)은 아래 Exception Filter가 생성한다.
---
@@ -97,12 +98,12 @@ export class ResponseInterceptor implements NestInterceptor {
```typescript
// common/filters/http-exception.filter.ts
@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
// 에러 코드 매핑 로직 포함
// HTTP 상태 → 에러 코드 매핑 후 { success: false, error: { code, message } } 반환
// SYS_001 / AUTH_001 / VAL_001 등 CLAUDE.md 에러 맵 참조
}
}
+3 -3
View File
@@ -88,13 +88,13 @@ export const useUserStore = defineStore('user', () => {
```typescript
// composables/useApi.ts — 중앙 Axios 인스턴스
const api = axios.create({
baseURL: import.meta.env.VITE_API_URL,
baseURL: import.meta.env.VITE_API_BASE_URL || '/api', // env.d.ts 에 타입 선언
withCredentials: true, // HttpOnly 쿠키 전송
})
// 응답 인터셉터 — 에러 코드 전역 처리
// 응답 인터셉터 — 표준 응답 언래핑 + 에러 코드 전역 처리
api.interceptors.response.use(
(response) => response.data.data, // success: true → data 언래핑
(response) => response.data.data, // { success, data } → data 언래핑
(error) => {
const code = error.response?.data?.error?.code
// 에러 코드별 Toast 알림 처리 (CLAUDE.md 에러 맵 참조)
+39
View File
@@ -0,0 +1,39 @@
# ==========================================
# 프로젝트 전역 설정
# ==========================================
PROJECT_NAME=prelude
APP_ENV=development
BUILD_MODE=development
FRONTEND_DIR=frontend
BACKEND_DIR=backend
# ==========================================
# 포트 설정
# ==========================================
FRONTEND_PORT=5173
BACKEND_PORT=3000
WEBSOCKET_PORT=3001
DB_PORT=5432
REDIS_PORT=6379
# ==========================================
# Database 설정
# ==========================================
DB_IMAGE=postgres:15-alpine
DB_USER=dev_ttipo
DB_PASSWORD=dev_960426
DB_NAME=prelude_db
DB_DATA_DIR=./data/postgres
# ==========================================
# Redis 설정
# ==========================================
REDIS_IMAGE=redis:7-alpine
REDIS_PASSWORD=dev_960426
REDIS_DATA_DIR=./data/redis
# ==========================================
# API 경로 설정
# ==========================================
BACKEND_API_URL=http://localhost:3000/api
CORS_ORIGIN=http://localhost:5173
+39
View File
@@ -0,0 +1,39 @@
# ==========================================
# 프로젝트 전역 설정
# ==========================================
PROJECT_NAME=prelude
APP_ENV=production
BUILD_MODE=production
FRONTEND_DIR=frontend
BACKEND_DIR=backend
# ==========================================
# 포트 설정
# ==========================================
FRONTEND_PORT=5173
BACKEND_PORT=3000
WEBSOCKET_PORT=3001
DB_PORT=5432
REDIS_PORT=6379
# ==========================================
# Database 설정
# ==========================================
DB_IMAGE=postgres:15-alpine
DB_USER=prd_ttipo
DB_PASSWORD=prd_960426
DB_NAME=prelude_db
DB_DATA_DIR=./data/postgres
# ==========================================
# Redis 설정
# ==========================================
REDIS_IMAGE=redis:7-alpine
REDIS_PASSWORD=prd_960426
REDIS_DATA_DIR=./data/redis
# ==========================================
# API 경로 설정
# ==========================================
BACKEND_API_URL=http://localhost:3000/api
CORS_ORIGIN=http://localhost:5173
+1
View File
@@ -3,6 +3,7 @@
# ==========================================
.env
.env.*
!.env.example
!.env.*.example
# ==========================================
+98 -19
View File
@@ -1,36 +1,115 @@
# 프로젝트 개요
이 저장소는 **풀스택 모노레포 기본 템플릿**이다. 누구나 클론하여 바로 개발을 시작할 수 있도록 공통 규칙과 언어별 규칙을 정의한다.
| 디렉터리 | 스택 | 역할 | 개발 규칙 (슬래시 명령) |
| ----------- | -------------- | ------------------------ | ---------------------------------------- |
| `backend/` | NestJS (TS) | REST API 서버 | `/nestjs` → @.claude/commands/nestjs.md |
| `frontend/` | Vue 3 (TS) | 웹 프론트엔드 | `/vue3` → @.claude/commands/vue3.md |
| `flutter/` | Flutter (Dart) | 모바일 앱 | `/flutter` → @.claude/commands/flutter.md |
| 루트 | Docker Compose | 로컬/운영 오케스트레이션 | @command.md |
세 클라이언트는 동일한 **표준 응답 포맷**과 **에러 코드 맵**을 공유한다 (아래 참조).
---
## 코딩 규칙
### 공통
- 모든 코드 주석은 **한국어**로 작성
-/서버는 100% TypeScript, Warning 방치 금지
- 유틸 함수는 `shared/utils/`에 순수 함수로 분리
-(`frontend`)·서버(`backend`)는 100% TypeScript, 모바일(`flutter`)은 Dart Sound Null Safety 준수
- 컴파일러/린터 **Warning 방치 금지** — 커밋 전 각 프로젝트 `lint` / `analyze` 통과
- 유틸 함수는 순수 함수로 분리 — TS는 `shared/utils/`, Dart는 `core/utils/`
- 매직 넘버·하드코딩 문자열 지양 → 상수 또는 환경변수로 분리
- 절대경로 alias 사용 (TS `@/`, Dart `package:`), 깊은 상대경로(`../../`) 지양
### 표준 응답 포맷 (Global Response Wrapper)
모든 API 응답은 아래 구조로 통일한다.
(SSOT: backend `TransformInterceptor` + `HttpExceptionFilter`)
```jsonc
// 성공
{ "success": true, "data": { /* ... */ } }
// 실패
{ "success": false, "error": { "code": "VAL_001", "message": "..." } }
```
- 프론트/플러터 인터셉터는 `success: true``data` 만 언래핑하여 반환한다.
- `success: false` 이거나 HTTP 4xx/5xx 면 `error.code` 로 메시지를 매핑한다.
### Git 커밋 컨벤션
feat: / fix: / refactor: / docs: / chore: / perf:
예: `feat: 납기오더 페이지네이션 추가`
`<type>: <제목>` 형식 — type: **feat / fix / refactor / docs / chore / perf / test / style**
- 예: `feat: 사용자 목록 페이지네이션 추가`
- 제목은 한국어, 명령형 현재 시제로 작성
- 한 커밋은 하나의 논리적 변경만 포함
### 에러 코드 맵핑
| 코드 | 상황 | 메시지 |
| -------- | ------------- | -------------------------------------- |
| SYS_001 | 500 Error | "일시적인 시스템 오류가 발생했습니다." |
| AUTH_001 | 401 | "로그인이 필요한 서비스입니다." |
| AUTH_002 | 세션 만료 | "안전을 위해 로그아웃 되었습니다." |
| AUTH_003 | 403 | "해당 기능에 접근 권한이 없습니다." |
| VAL_001 | Validation | "입력하신 정보를 다시 확인해 주세요." |
| RES_001 | 404 | "요청하신 정보를 찾을 수 없습니다." |
| BIZ_001 | 비즈니스 오류 | "요청하신 작업을 완료하지 못했습니다." |
세 플랫폼이 공유하는 **단일 진실 공급원(SSOT)**. 메시지를 수정할 때는 아래 세 파일을 함께 갱신한다.
- backend: `src/common/filters/http-exception.filter.ts`
- frontend: `src/composables/useApi.ts` (`ErrorCodeLexicon`)
- flutter: `lib/core/api/error_lexicon.dart` (`ErrorLexicon`)
| 코드 | HTTP / 상황 | 메시지 |
| -------- | -------------------- | ------------------------------------------------------- |
| SYS_001 | 500 / 미정의 예외 | "일시적인 시스템 오류가 발생했습니다. 잠시 후 다시 시도해 주세요." |
| AUTH_001 | 401 | "로그인이 필요한 서비스입니다. 로그인 후 이용해 주세요." |
| AUTH_002 | 세션 만료 (클라이언트) | "안전을 위해 로그아웃 되었습니다. 다시 로그인해 주세요." |
| AUTH_003 | 403 | "해당 메뉴나 기능에 접근할 수 있는 권한이 없습니다." |
| VAL_001 | 400 / 422 (Validation) | "입력하신 정보를 다시 확인해 주세요. (필수값 누락 또는 형식 오류)" |
| RES_001 | 404 | "요청하신 정보나 페이지를 찾을 수 없습니다." |
| BIZ_001 | 200(비즈니스 실패) / 기타 | "요청하신 작업을 완료하지 못했습니다. 다시 시도해 주세요." |
> 비즈니스 예외는 `HttpException` 응답의 `message` 로 사용자 노출 문구를 재정의할 수 있다.
### 보안
- `.env` 민감정보 Git 커밋 금지
- JWT는 HttpOnly Secure 쿠키 사용 (localStorage 금지)
- TypeORM ORM 쿼리 사용으로 SQL Injection 차단
- Docker 컨테이너 내부 root 실행 금지
- `.env*` 민감정보 Git 커밋 금지 (`.gitignore` 확인) — 예시는 `*.example`(예: `.env.development.example`, `.env.example`) 로만 공유
- JWT는 HttpOnly·Secure 쿠키 사용 (`localStorage` 금지) / 모바일은 `flutter_secure_storage` 사용
- TypeORM ORM 쿼리 사용으로 SQL Injection 차단 (Raw 쿼리 시 파라미터 바인딩 필수)
- 입력값은 DTO(`class-validator`) 검증을 거친 뒤 서비스 계층에 전달
- 보안 헤더(`helmet`)·CORS·Rate Limiting(`@nestjs/throttler`) 적용
- Docker 컨테이너 내부 root 실행 금지 (비-root 사용자로 구동)
---
## 환경변수 구조 (단일 진실 공급원: 루트 `.env`)
이 템플릿은 **루트 `.env.{APP_ENV}` 가 모든 설정의 단일 진실 공급원(SSOT)** 이다.
값을 한 곳에서만 관리하기 위해, 서비스(backend/frontend)는 자체 env에 값을 중복 작성하지 않고 **루트 → docker-compose 가 주입한 값을 그대로 사용**한다.
```
루트 .env.{APP_ENV}
│ (docker compose --env-file 로 로드)
├─▶ backend : env_file + environment 로 DB/Redis/CORS/포트 주입
│ (backend/.env.{APP_ENV} 는 "존재"만 하면 됨, 내용 최소)
├─▶ frontend : BACKEND_API_URL → build arg(VITE_API_BASE_URL) 로 주입
├─▶ db : DB_USER/DB_PASSWORD/DB_NAME, DB_DATA_DIR 볼륨
└─▶ redis : REDIS_PASSWORD, REDIS_DATA_DIR 볼륨
```
규칙:
- **새 설정값은 루트 `.env.{APP_ENV}` 에 먼저 추가**하고, 필요한 서비스에 `docker-compose.yml``environment` / `args` 로 전달한다.
- 서비스 자체 env 파일에는 **루트에서 내려주지 않는 고유 값만** 작성한다. (예: 백엔드 `JWT_SECRET`)
- `docker-compose.yml``env_file` 지시자 때문에 `backend/.env.{APP_ENV}` 파일은 비어 있어도 **존재해야 한다.**
- **단독 실행(non-Docker)** 시에는 주입이 없으므로, 각 서비스 `.env.{APP_ENV}` 의 주석 처리된 예시 값을 활성화해 사용한다.
- **Flutter 는 예외** — docker-compose 대상이 아니므로 `flutter/.env` 에서 `API_URL` 을 독립적으로 관리한다.
- 모든 env 파일은 커밋 금지, `*.example` 만 커밋한다. (`.gitignore``!*.example` 재포함 규칙)
> 루트 env의 대표 키: `PROJECT_NAME`, `APP_ENV`, `BUILD_MODE`, 각종 `*_PORT`, `BACKEND_API_URL`, `CORS_ORIGIN`, `DB_*`/`DB_DATA_DIR`, `REDIS_*`/`REDIS_DATA_DIR`. 실제 목록은 `.env.development.example` 참조.
---
## 개발 규칙 참조
@.claude/commands/nestjs.md
@.claude/commands/vue3.md
- 백엔드(NestJS): @.claude/commands/nestjs.md
- 프론트엔드(Vue 3): @.claude/commands/vue3.md
- 모바일(Flutter): @.claude/commands/flutter.md
- 실행/배포 명령어: @command.md
+109
View File
@@ -0,0 +1,109 @@
# 풀스택 모노레포 기본 템플릿
NestJS(백엔드) · Vue 3(웹) · Flutter(모바일)를 하나의 저장소에서 운영하는 풀스택 템플릿입니다.
세 클라이언트는 **동일한 표준 응답 포맷**과 **공통 에러 코드 맵**을 공유하며, Docker Compose로 한 번에 구동됩니다.
## 구성
| 디렉터리 | 스택 | 역할 | 포트(기본) |
| ----------- | -------------- | ------------------------ | ---------- |
| `backend/` | NestJS + TypeORM | REST API 서버 | 3000 |
| `frontend/` | Vue 3 + Vite | 웹 프론트엔드 | 5173 |
| `flutter/` | Flutter + Riverpod | 모바일 앱 | - |
| `db` | PostgreSQL | 데이터베이스 | 5432 |
| `redis` | Redis | 캐시 / 세션 | 6379 |
## 사전 요구사항
- Docker / Docker Compose (권장 구동 방식)
- 단독 실행 시: Node.js `^20.19 || >=22.12`, Flutter SDK `^3.11`
## 빠른 시작 (Docker)
```bash
# 1) 환경변수 파일 생성 (예제 복사)
# 실제 설정값은 루트 .env.development 한 곳에서 관리한다 (SSOT).
# backend/frontend 는 docker-compose 가 값을 주입하므로 예제만 복사하면 된다.
cp .env.development.example .env.development # ← 여기에만 값 입력
cp backend/.env.development.example backend/.env.development # (존재만 필요, 내용 최소)
cp frontend/.env.development.example frontend/.env.development # (단독 실행 시에만 사용)
# 2) 개발 환경 기동 (코드만 수정 시)
docker compose -f docker-compose.yml -f docker-compose.dev.yml --env-file .env.development up -d
# 3) 의존성 변경 시에만 --build
docker compose -f docker-compose.yml -f docker-compose.dev.yml --env-file .env.development up -d --build
```
기동 후:
- 웹: <http://localhost:5173>
- API: <http://localhost:3000/api>
- Swagger 문서: <http://localhost:3000/api-docs>
전체 실행/배포 명령어는 [`command.md`](./command.md) 참조.
## 단독 실행 (Docker 미사용)
```bash
# 백엔드
cd backend && npm install && npm run start:dev
# 프론트엔드
cd frontend && npm install && npm run dev
# 모바일
cd flutter && cp .env.example .env && flutter pub get && flutter run
```
> 단독 실행 시 백엔드 `.env.development` 의 `DB_HOST`/`REDIS_HOST` 를 `localhost` 로 두고,
> PostgreSQL·Redis 를 별도로 띄워야 합니다.
## 환경변수
`.env*` 는 커밋하지 않습니다. 각 디렉터리의 `*.example` 파일을 복사해 사용하세요.
**루트 `.env.{env}` 가 단일 진실 공급원(SSOT)** 입니다. backend·frontend 는 docker-compose 가
루트 값을 주입하므로 자체 env 에 값을 중복 작성하지 않습니다. (자세한 위임 구조는 [`CLAUDE.md`](./CLAUDE.md) 의 "환경변수 구조" 참조)
| 파일 | 용도 | 값 작성 위치 |
| ----------------------------- | ---------------------------------------- | --------------------- |
| `.env.{env}.example` | 루트 — 모든 설정의 SSOT (compose 주입) | ✅ 여기에 작성 |
| `backend/.env.{env}.example` | 백엔드 — 고유 시크릿(JWT 등)만 | 최소 (존재 필수) |
| `frontend/.env.{env}.example` | 프론트 — 단독 실행용 `VITE_API_BASE_URL` | 단독 실행 시에만 |
| `flutter/.env.example` | 모바일 — `API_URL` (compose 비대상) | ✅ 독립 관리 |
`{env}` = `development` | `production`
## 표준 응답 포맷
```jsonc
// 성공
{ "success": true, "data": { /* ... */ } }
// 실패
{ "success": false, "error": { "code": "VAL_001", "message": "..." } }
```
에러 코드 맵과 코딩 규칙 전문은 [`CLAUDE.md`](./CLAUDE.md) 및 `.claude/commands/` 의 언어별 규칙 문서를 참고하세요.
## 개발 규칙
| 영역 | 문서 |
| --------- | ------------------------------------------------- |
| 공통/보안 | [`CLAUDE.md`](./CLAUDE.md) |
| NestJS | [`.claude/commands/nestjs.md`](./.claude/commands/nestjs.md) |
| Vue 3 | [`.claude/commands/vue3.md`](./.claude/commands/vue3.md) |
| Flutter | [`.claude/commands/flutter.md`](./.claude/commands/flutter.md) |
## 디렉터리 구조
```
.
├── backend/ # NestJS API (Controller / Service / DTO / 공통 필터·인터셉터)
├── frontend/ # Vue 3 (pages / composables / stores / router)
├── flutter/ # Flutter (core / features / widgets)
├── docker-compose.yml # 베이스 정의
├── docker-compose.dev.yml # 개발 오버라이드 (핫리로드, 포트 노출)
├── CLAUDE.md # 공통 코딩 규칙 + 에러 코드 맵 (SSOT)
└── command.md # 실행/배포 명령어
```
+34
View File
@@ -0,0 +1,34 @@
# ==========================================
# 백엔드 환경변수 (NestJS) — 개발용
# 사용법: cp .env.development.example .env.development
#
# ⚠️ 도커로 실행하면 이 파일에 값을 적을 필요가 거의 없다.
# 루트 .env.development → docker-compose 가 아래 값을 주입(override)한다:
# NODE_ENV, PORT, WEBSOCKET_PORT, CORS_ORIGIN,
# DB_HOST(=db), DB_PORT(=5432), DB_USER, DB_PASSWORD, DB_NAME,
# REDIS_HOST(=redis), REDIS_PORT(=6379), REDIS_PASSWORD
# (docker-compose 의 env_file 지시자 때문에 이 파일은 "존재"해야 한다.)
#
# 아래 항목은 ⓐ 백엔드 단독(npm run start:dev) 실행 시 또는
# ⓑ 루트에서 내려주지 않는 서비스 고유 값일 때만 채운다.
# ==========================================
# ----- 단독 실행(non-Docker) 시에만 필요 — 도커 실행 시 무시됨 -----
# NODE_ENV=development
# PORT=3000
# WEBSOCKET_PORT=3001
# CORS_ORIGIN=http://localhost:5173
# DB_HOST=localhost
# DB_PORT=5432
# DB_USER=dev_ttipo
# DB_PASSWORD=dev_960426
# DB_NAME=prelude_db
# REDIS_HOST=localhost
# REDIS_PORT=6379
# REDIS_PASSWORD=dev_960426
# ----- 백엔드 고유 값 (루트에서 내려주지 않음 / 인증 구현 시 사용) -----
# JWT_SECRET=changeme_jwt_secret
# JWT_EXPIRES_IN=1h
# JWT_REFRESH_SECRET=changeme_refresh_secret
# JWT_REFRESH_EXPIRES_IN=7d
+17
View File
@@ -0,0 +1,17 @@
# ==========================================
# 백엔드 환경변수 (NestJS) — 운영용
# 사용법: cp .env.production.example .env.production
#
# ⚠️ 운영은 도커로 구동된다. DB/Redis/CORS/포트 등은 루트 .env.production →
# docker-compose 가 모두 주입하므로 여기에 중복 작성하지 않는다.
# (env_file 지시자 때문에 이 파일은 "존재"하기만 하면 된다.)
#
# 아래는 루트에서 내려주지 않는 백엔드 고유 시크릿이다. 인증 구현 시 채운다.
# ⚠️ 반드시 강력한 임의값으로 교체할 것.
# ==========================================
# ----- 백엔드 고유 값 (인증 구현 시 사용) -----
# JWT_SECRET=__CHANGE_ME_STRONG_JWT_SECRET__
# JWT_EXPIRES_IN=15m
# JWT_REFRESH_SECRET=__CHANGE_ME_STRONG_REFRESH_SECRET__
# JWT_REFRESH_EXPIRES_IN=7d
+29 -12
View File
@@ -1,18 +1,35 @@
### 개발 환경 명령어
# 실행 / 배포 명령어
- 코드만 수정했을 때 (의존성 변경 없음)
docker compose -f docker-compose.yml -f docker-compose.dev.yml --env-file .env.development up -d
> Docker Compose 기반 로컬·운영 오케스트레이션 명령어 모음.
> `.env.development` / `.env.production` 는 커밋된 `.env.*.example` 를 복사해 생성한다.
> (`.gitignore` 는 `.env.*` 를 무시하되 `.env.*.example` 만 추적한다.)
- 의존성 변경 시에만 --build
docker compose -f docker-compose.yml -f docker-compose.dev.yml --env-file .env.development up -d --build
## 개발 환경
### 운영 환경 명령어
```bash
# 코드만 수정했을 때 (의존성 변경 없음)
docker compose -f docker-compose.yml -f docker-compose.dev.yml --env-file .env.development up -d
- 운영 환경 배포 명령어
sudo docker compose --env-file .env.production up -d --build
# 의존성 변경 시에만 --build
docker compose -f docker-compose.yml -f docker-compose.dev.yml --env-file .env.development up -d --build
- 로그 확인
sudo docker compose --env-file .env.development logs -f backend
# 로그 확인 (예: backend)
docker compose -f docker-compose.yml -f docker-compose.dev.yml --env-file .env.development logs -f backend
```
- ngrok 도메인 포트 열기
ngrok http --domain=ttipo-backend.ngrok.app 3000
## 운영 환경
```bash
# 운영 환경 배포
sudo docker compose --env-file .env.production up -d --build
# 로그 확인
sudo docker compose --env-file .env.production logs -f backend
```
## 외부 노출 (선택)
```bash
# ngrok 으로 백엔드 포트(3000) 임시 노출 — <YOUR_DOMAIN> 을 본인 도메인으로 교체
ngrok http --domain=<YOUR_DOMAIN>.ngrok.app 3000
```
+3 -3
View File
@@ -9,7 +9,7 @@ services:
dockerfile: Dockerfile
args:
- BUILD_MODE=${BUILD_MODE}
- VITE_API_BASE_URL=${FRONT_API_URL}
- VITE_API_BASE_URL=${BACKEND_API_URL}
expose:
- "80"
environment:
@@ -92,7 +92,7 @@ services:
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: ${DB_NAME}
volumes:
- ./db-data:/var/lib/postgresql/data
- ${DB_DATA_DIR}:/var/lib/postgresql/data
restart: always
networks:
- app-network
@@ -120,7 +120,7 @@ services:
expose:
- "6379"
volumes:
- ./redis-data:/data
- ${REDIS_DATA_DIR}:/data
restart: always
networks:
- app-network
+12
View File
@@ -0,0 +1,12 @@
# ==========================================
# Flutter 환경변수 (flutter_dotenv) — pubspec assets 에 .env 등록됨
# 사용법: cp .env.example .env
#
# ⚠️ 모바일 앱은 docker-compose 대상이 아니므로 루트 .env 의 위임을 받지 않는다.
# 아래 값은 항상 이 파일에서 직접 관리한다.
# 빌드 타임 분리가 필요하면 --dart-define 또는 .env.{flavor} 도입 고려.
# ==========================================
# 백엔드 API 베이스 URL (백엔드 글로벌 prefix /api 포함)
# 안드로이드 에뮬레이터에서 호스트 접근 시: http://10.0.2.2:3000/api
API_URL=http://localhost:3000/api
+12
View File
@@ -0,0 +1,12 @@
# ==========================================
# 프론트엔드 환경변수 (Vite) — 개발용
# 사용법: cp .env.development.example .env.development
#
# ⚠️ 도커로 빌드/실행하면 VITE_API_BASE_URL 은 루트 .env 의 BACKEND_API_URL 이
# docker-compose build arg 로 주입되므로 이 파일은 비워도 된다.
# 아래 값은 프론트 단독(npm run dev) 실행 시에만 사용된다.
# (VITE_ 접두사가 붙은 값만 클라이언트 번들에 노출 — 비밀값 금지)
# ==========================================
# 백엔드 API 베이스 URL (백엔드 글로벌 prefix /api 포함)
VITE_API_BASE_URL=http://localhost:3000/api
+10
View File
@@ -0,0 +1,10 @@
# ==========================================
# 프론트엔드 환경변수 (Vite) — 운영용
# 사용법: cp .env.production.example .env.production
#
# ⚠️ 도커 빌드 시에는 루트 .env 의 BACKEND_API_URL(build arg)이 우선 적용된다.
# 아래 값은 프론트 단독 빌드(npm run build) 시에만 사용된다.
# ==========================================
# 운영 도메인 기준 API 베이스 URL
VITE_API_BASE_URL=https://api.example.com/api