Fix: Use NEXT_PUBLIC_API_URL for all image URLs
This commit is contained in:
230
DEPLOYMENT_CHECKLIST.md
Normal file
230
DEPLOYMENT_CHECKLIST.md
Normal file
@@ -0,0 +1,230 @@
|
||||
# AutonetSellCar 배포 체크리스트
|
||||
|
||||
## 환경 정보
|
||||
|
||||
| 항목 | 서버4 (개발) | 서버2 (운영) |
|
||||
|------|-------------|-------------|
|
||||
| IP | 192.168.0.204 | 192.168.0.202 |
|
||||
| OS | Windows | Ubuntu 22.04 |
|
||||
| Frontend 포트 | 3000 | 3000 (운영), 3001 (스테이징) |
|
||||
| Backend 포트 | 8000 | 8000 (운영), 8001 (스테이징) |
|
||||
|
||||
---
|
||||
|
||||
## 1. 서버2 디렉토리 구조
|
||||
|
||||
```
|
||||
/opt/autonet/
|
||||
├── git/autonet.git/ # Git bare repository
|
||||
├── staging/ # 스테이징 코드
|
||||
├── production/ # 운영 코드
|
||||
│ ├── frontend/
|
||||
│ │ ├── Dockerfile
|
||||
│ │ ├── .env.production # ★ NEXT_PUBLIC_API_URL 설정
|
||||
│ │ └── ...
|
||||
│ ├── backend/
|
||||
│ │ ├── Dockerfile
|
||||
│ │ ├── autonet.db # ★ 데이터베이스
|
||||
│ │ └── uploads/ # ★ 이미지 파일
|
||||
│ └── docker-compose.production.yml
|
||||
├── releases/ # 롤백용 아카이브
|
||||
├── scripts/ # 배포 스크립트
|
||||
└── logs/ # 로그
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 핵심 설정 파일
|
||||
|
||||
### 2.1 Frontend 환경변수 (.env.production)
|
||||
|
||||
**파일 위치**: `/opt/autonet/production/frontend/.env.production`
|
||||
|
||||
```env
|
||||
NEXT_PUBLIC_API_URL=http://192.168.0.202:8000
|
||||
```
|
||||
|
||||
⚠️ **중요**: Next.js는 **빌드 시점**에 `NEXT_PUBLIC_*` 변수를 코드에 포함시킵니다.
|
||||
- 런타임에 환경변수를 바꿔도 적용 안됨
|
||||
- 환경변수 변경 후 반드시 **이미지 재빌드** 필요
|
||||
|
||||
### 2.2 Dockerfile (Frontend)
|
||||
|
||||
**파일 위치**: `/opt/autonet/production/frontend/Dockerfile`
|
||||
|
||||
빌드 시 환경변수 적용을 위해 다음이 포함되어야 함:
|
||||
|
||||
```dockerfile
|
||||
# builder 단계에서
|
||||
ARG NEXT_PUBLIC_API_URL=http://192.168.0.202:8000
|
||||
ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL
|
||||
```
|
||||
|
||||
### 2.3 docker-compose.production.yml
|
||||
|
||||
```yaml
|
||||
version: '3.8'
|
||||
services:
|
||||
frontend:
|
||||
build: ./frontend
|
||||
container_name: autonet-frontend
|
||||
ports:
|
||||
- "3000:3000"
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
depends_on:
|
||||
- backend
|
||||
|
||||
backend:
|
||||
build: ./backend
|
||||
container_name: autonet-backend
|
||||
ports:
|
||||
- "8000:8000"
|
||||
volumes:
|
||||
- ./backend/uploads:/app/uploads # ★ 이미지 영속화
|
||||
- ./backend/autonet.db:/app/autonet.db # ★ DB 영속화
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 데이터 이전 체크리스트
|
||||
|
||||
### 3.1 데이터베이스 (autonet.db)
|
||||
|
||||
| 단계 | 명령어 | 확인 |
|
||||
|------|--------|------|
|
||||
| 서버4에서 복사 | `scp backend/autonet.db damon@192.168.0.202:/tmp/` | [ ] |
|
||||
| 서버2 컨테이너에 복사 | `docker cp /tmp/autonet.db autonet-backend:/app/` | [ ] |
|
||||
| 파일 확인 | `docker exec autonet-backend ls -la /app/autonet.db` | [ ] |
|
||||
| 백엔드 재시작 | `docker restart autonet-backend` | [ ] |
|
||||
|
||||
### 3.2 업로드 이미지 (uploads/)
|
||||
|
||||
| 단계 | 명령어 | 확인 |
|
||||
|------|--------|------|
|
||||
| 서버4에서 압축 | `tar -cvf uploads.tar uploads` | [ ] |
|
||||
| 서버2로 복사 | `scp uploads.tar damon@192.168.0.202:/tmp/` | [ ] |
|
||||
| 압축 해제 | `cd /tmp && tar -xvf uploads.tar` | [ ] |
|
||||
| 컨테이너에 복사 | `docker cp /tmp/uploads/. autonet-backend:/app/uploads/` | [ ] |
|
||||
| 파일 확인 | `docker exec autonet-backend ls -la /app/uploads/` | [ ] |
|
||||
|
||||
---
|
||||
|
||||
## 4. 이미지 URL 문제 디버깅
|
||||
|
||||
### 4.1 현재 문제
|
||||
|
||||
브라우저에서 이미지 요청 URL이 `http://localhost:8000/...`으로 되어 있음.
|
||||
→ 다른 PC에서 접속하면 localhost가 그 PC를 가리키므로 이미지 안 보임.
|
||||
|
||||
### 4.2 올바른 이미지 URL
|
||||
|
||||
```
|
||||
http://192.168.0.202:8000/uploads/cars/39/image_0.jpg
|
||||
```
|
||||
|
||||
### 4.3 원인 분석
|
||||
|
||||
1. **api.ts에서 API_BASE_URL 설정 확인**
|
||||
- 파일: `frontend/src/lib/api.ts`
|
||||
- `NEXT_PUBLIC_API_URL` 환경변수 사용 여부
|
||||
|
||||
2. **이미지 URL 생성 로직 확인**
|
||||
- 이미지 URL이 어디서 생성되는지
|
||||
- 백엔드에서 전체 URL 반환? vs 프론트엔드에서 조합?
|
||||
|
||||
3. **빌드 시점 환경변수 확인**
|
||||
- `.env.production` 파일 존재 여부
|
||||
- Dockerfile에서 ARG/ENV 설정 여부
|
||||
|
||||
---
|
||||
|
||||
## 5. 디버깅 명령어
|
||||
|
||||
### 5.1 프론트엔드 환경변수 확인
|
||||
|
||||
```bash
|
||||
# 컨테이너 환경변수
|
||||
docker exec autonet-frontend printenv | grep API
|
||||
|
||||
# .env.production 파일 내용
|
||||
cat /opt/autonet/production/frontend/.env.production
|
||||
```
|
||||
|
||||
### 5.2 백엔드 API 테스트
|
||||
|
||||
```bash
|
||||
# 이미지 직접 접근 테스트
|
||||
curl -I http://192.168.0.202:8000/uploads/cars/39/image_0.jpg
|
||||
|
||||
# API 응답에서 이미지 URL 확인
|
||||
curl http://192.168.0.202:8000/api/cars/39 | jq '.images'
|
||||
```
|
||||
|
||||
### 5.3 컨테이너 상태 확인
|
||||
|
||||
```bash
|
||||
docker ps -a
|
||||
docker logs autonet-frontend --tail 20
|
||||
docker logs autonet-backend --tail 20
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 재빌드 절차
|
||||
|
||||
### 6.1 Frontend 재빌드 (환경변수 변경 시)
|
||||
|
||||
```bash
|
||||
cd /opt/autonet/production
|
||||
|
||||
# 1. 컨테이너 중지/삭제
|
||||
docker rm -f autonet-frontend
|
||||
|
||||
# 2. 이미지 삭제
|
||||
docker rmi production_frontend
|
||||
|
||||
# 3. .env.production 확인
|
||||
cat frontend/.env.production
|
||||
|
||||
# 4. 재빌드
|
||||
docker build --no-cache -t production_frontend ./frontend
|
||||
|
||||
# 5. 실행
|
||||
docker run -d \
|
||||
--name autonet-frontend \
|
||||
--network autonet-production-network \
|
||||
-p 3000:3000 \
|
||||
production_frontend
|
||||
|
||||
# 6. 확인
|
||||
docker ps | grep frontend
|
||||
docker logs autonet-frontend --tail 10
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 현재 상태 점검 명령어
|
||||
|
||||
서버2에서 순서대로 실행:
|
||||
|
||||
```bash
|
||||
# 1. 컨테이너 상태
|
||||
docker ps -a
|
||||
|
||||
# 2. 프론트엔드 환경변수
|
||||
docker exec autonet-frontend printenv | grep -E "(API|NODE)"
|
||||
|
||||
# 3. .env.production 파일
|
||||
cat /opt/autonet/production/frontend/.env.production
|
||||
|
||||
# 4. Dockerfile 환경변수 설정
|
||||
grep -A2 "ARG NEXT_PUBLIC" /opt/autonet/production/frontend/Dockerfile
|
||||
grep "ENV NEXT_PUBLIC" /opt/autonet/production/frontend/Dockerfile
|
||||
|
||||
# 5. 백엔드 이미지 파일 존재 확인
|
||||
docker exec autonet-backend ls /app/uploads/cars/ | head -5
|
||||
|
||||
# 6. 백엔드 이미지 직접 접근 테스트
|
||||
curl -I http://localhost:8000/uploads/cars/39/image_0.jpg
|
||||
```
|
||||
BIN
backend/uploads.tar
Normal file
BIN
backend/uploads.tar
Normal file
Binary file not shown.
@@ -103,13 +103,15 @@ const FUEL_TYPES = [
|
||||
{ value: 'LPG', label: 'LPG' },
|
||||
];
|
||||
|
||||
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000';
|
||||
|
||||
// 이미지 URL 변환 (로컬 경로는 백엔드 URL 추가)
|
||||
const getImageUrl = (url: string | undefined): string => {
|
||||
if (!url) return '';
|
||||
if (url.startsWith('http://') || url.startsWith('https://')) {
|
||||
return url;
|
||||
}
|
||||
return `http://localhost:8000${url}`;
|
||||
return `${API_BASE_URL}${url}`;
|
||||
};
|
||||
|
||||
const YEAR_OPTIONS = Array.from({ length: 15 }, (_, i) => 2024 - i);
|
||||
|
||||
@@ -33,13 +33,15 @@ const defaultFormData: BannerFormData = {
|
||||
car_id: null,
|
||||
};
|
||||
|
||||
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000';
|
||||
|
||||
// 이미지 URL 변환 (로컬 경로는 백엔드 URL 추가)
|
||||
const getImageUrl = (url: string | undefined): string => {
|
||||
if (!url) return '';
|
||||
if (url.startsWith('http://') || url.startsWith('https://')) {
|
||||
return url;
|
||||
}
|
||||
return `http://localhost:8000${url}`;
|
||||
return `${API_BASE_URL}${url}`;
|
||||
};
|
||||
|
||||
export default function HeroBannersPage() {
|
||||
@@ -97,7 +99,7 @@ export default function HeroBannersPage() {
|
||||
.map((b: any) => b.car_id);
|
||||
if (carIds.length > 0) {
|
||||
try {
|
||||
const response = await fetch('http://localhost:8000/api/carmodoo/pdf-status', {
|
||||
const response = await fetch(`${API_BASE_URL}/api/carmodoo/pdf-status`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(carIds),
|
||||
|
||||
@@ -324,6 +324,8 @@ interface BannerCardProps {
|
||||
height: number;
|
||||
}
|
||||
|
||||
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000';
|
||||
|
||||
// 이미지 URL 변환 (로컬 경로는 백엔드 URL 추가)
|
||||
const getImageUrl = (url: string): string => {
|
||||
if (!url) return '';
|
||||
@@ -331,7 +333,7 @@ const getImageUrl = (url: string): string => {
|
||||
return url;
|
||||
}
|
||||
// 로컬 경로인 경우 백엔드 URL 추가
|
||||
return `http://localhost:8000${url}`;
|
||||
return `${API_BASE_URL}${url}`;
|
||||
};
|
||||
|
||||
// Helper to get localized title/subtitle based on language
|
||||
|
||||
Reference in New Issue
Block a user