Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
name: Deploy to Docker Hub and EC2

on:
push:
branches: [ deploy ]
workflow_dispatch:

jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ secrets.DOCKERHUB_USERNAME }}/toeic-center-finder:latest
build-args: |
REACT_APP_GOOGLE_MAPS_API_KEY=${{ secrets.REACT_APP_GOOGLE_MAPS_API_KEY }}

deploy:
needs: build-and-push
runs-on: ubuntu-latest
steps:
- name: Setup SSH
uses: webfactory/ssh-agent@v0.9.0
with:
ssh-private-key: ${{ secrets.EC2_SSH_KEY }}

- name: Add SSH known_hosts
run: |
mkdir -p ~/.ssh
ssh-keyscan -H ${{ secrets.EC2_HOST }} >> ~/.ssh/known_hosts

- name: Deploy with Docker
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USER }}
key: ${{ secrets.EC2_SSH_KEY }}
script: |
cd ~/location-map
docker pull ${{ secrets.DOCKERHUB_USERNAME }}/toeic-center-finder:latest
docker stop toeic-center-finder || true
docker rm toeic-center-finder || true
docker run -d --name toeic-center-finder -p 3000:80 -p 4000:4000 --restart unless-stopped ${{ secrets.DOCKERHUB_USERNAME }}/toeic-center-finder:latest
docker system prune -af
42 changes: 42 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# 통합 Dockerfile
FROM node:18 as build

# React 앱 빌드
WORKDIR /app/frontend
COPY location-map-app/package*.json ./
RUN npm install
COPY location-map-app/ ./
ARG REACT_APP_GOOGLE_MAPS_API_KEY
ENV REACT_APP_GOOGLE_MAPS_API_KEY=$REACT_APP_GOOGLE_MAPS_API_KEY
RUN npm run build

# API 서버 설정 (여기서는 의존성만 복사하고 설치)
WORKDIR /app/api
COPY toeic-api/package*.json ./
RUN npm install
COPY toeic-api/ ./

# 최종 이미지
FROM nginx:alpine

# Node.js 설치
RUN apk add --update --no-cache nodejs npm curl

# Nginx 설정 및 빌드된 React 앱 복사
COPY --from=build /app/frontend/build /usr/share/nginx/html/location-map-app
COPY location-map-app/nginx.conf /etc/nginx/conf.d/default.conf

# API 서버 설치 (node_modules 없이)
WORKDIR /app/api
COPY toeic-api/package*.json ./
COPY toeic-api/*.js ./

# 로그 디렉토리 생성
RUN mkdir -p /var/log/api /var/log/nginx

# 시작 스크립트 복사
COPY start.sh /start.sh
RUN chmod +x /start.sh

EXPOSE 80 4000
CMD ["/start.sh"]
56 changes: 56 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<p align="center">
<img src="https://github.com/user-attachments/assets/6f56fa66-1bf9-4c7c-b675-dd5c9e65f7bb" width=200/>
</p>

<div align="center">

### 내 근처 토익 시험장 찾기

토익 시험장, 신청할 때 마다 근처 시험장을 찾느라 고생하셨나요?
현재 위치 기준 고사장 위치를 정렬해주는 웹사이트를 만나보세요!

<br/>

<img width="800" alt="스크린샷 2025-04-11 오전 12 36 34" src="https://github.com/user-attachments/assets/5be4c565-f8dc-4531-a4ab-0ab42259d001" />



**내 근처 토익 시험장 찾으러 가기!**
https://bevibing.duckdns.org/location-map-app/



*토익 홈페이지가 점검 중인 경우 오류가 발생할 수 있습니다.*


</div>


---
### 기술 스택


**[ 해당 웹사이트는 AI와 함께 개발하였습니다. ]**

[![My Skills](https://skillicons.dev/icons?i=html,css,ts,react,firebase,aws,nginx)](https://skillicons.dev)

<br/>

[[개발 여행기1 - 내 근처에 있는 토익 시험장이 궁금하다]](https://velog.io/@_roundtable/%EB%B9%84%EB%B0%94%EC%9D%B4%EB%B9%99-%ED%86%A0%EC%9D%B5-%EC%8B%9C%ED%97%98%EC%9E%A5-%EC%B0%BE%EA%B8%B0-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-1)

[[개발 여행기2 - 도메인 네임이 맘에 안들어서 어쩌지]](https://velog.io/@_roundtable/%EB%B9%84%EB%B0%94%EC%9D%B4%EB%B9%99-%ED%86%A0%EC%9D%B5-%EC%8B%9C%ED%97%98%EC%9E%A5-%EC%B0%BE%EA%B8%B0-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-2)

<br/>

<div>

- 리액트/ts로 프론트 페이지 개발
- CORS 에러 우회를 위한 Firebase Functions 이용
- 개인 도메인 설정 및 HTTPS 인증서 적용을 위해 EC2 + nginx + Let's Encrypt
</div>

<br/>




51 changes: 27 additions & 24 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,29 +1,32 @@
version: "3"
version: "3.8"
services:
react:
toeic-center-finder:
build:
context: ./location-map-app
volumes:
- react_build:/app/build
command: ["true"]

api:
build:
context: ./toeic-api
context: .
dockerfile: Dockerfile
args:
- REACT_APP_GOOGLE_MAPS_API_KEY=${REACT_APP_GOOGLE_MAPS_API_KEY:-your_default_api_key}
# Docker Hub 배포를 위한 설정
image: ${DOCKER_USERNAME:-yourname}/toeic-center-finder:${TAG:-latest}
container_name: toeic-center-finder
ports:
- "4000:4000"
- "${HOST_PORT:-80}:80"
- "${API_HOST_PORT:-4000}:4000"
restart: unless-stopped
# 개발 환경에서만 소스 코드 마운트 (NODE_ENV가 development일 때만)
volumes:
- ./location-map-app/src:/app/frontend/src:ro
# API 디렉토리를 마운트하지만 node_modules는 제외
# 읽기 전용이 아닌 rw로 변경
- ./toeic-api:/app/api:rw
# 환경 변수 설정
environment:
- NODE_ENV=${NODE_ENV:-production}
- PORT=4000

nginx:
image: nginx:alpine
depends_on:
- api
ports:
- "80:80"
volumes:
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
- react_build:/usr/share/nginx/html

volumes:
react_build:
# 컨테이너 상태 확인
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:4000/api/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
13 changes: 9 additions & 4 deletions location-map-app/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
# 리액트용 Dockerfile (빌드 전용)
FROM node:18 AS build
# Dockerfile
FROM node:18 as build
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
ARG REACT_APP_GOOGLE_MAPS_API_KEY
ENV REACT_APP_GOOGLE_MAPS_API_KEY=$REACT_APP_GOOGLE_MAPS_API_KEY
# 아래 줄 수정 필요 - nginx 경로 설정을 위한 PUBLIC_URL 추가
RUN REACT_APP_PUBLIC_URL="/location-map-app" npm run build
RUN npm run build

FROM nginx:alpine
COPY build/ /usr/share/nginx/html/location-map-app/
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
41 changes: 41 additions & 0 deletions location-map-app/nginx.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
server {
listen 80;
# 로컬 환경과 배포 환경 모두 지원하도록 서버 이름 설정
server_name localhost bevibing.duckdns.org;

location /location-map-app/ {
root /usr/share/nginx/html;
index index.html;

try_files $uri $uri/ /location-map-app/index.html;
}

# API 요청을 내부 Node.js 서버로 프록시
location /api/ {
# Docker 컨테이너 내부에서도 동작하도록 수정
proxy_pass http://127.0.0.1:4000;

proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_cache_bypass $http_upgrade;

# 타임아웃 설정
proxy_connect_timeout 60s;
proxy_send_timeout 120s;
proxy_read_timeout 120s;

# 디버깅 로그 활성화
proxy_intercept_errors on;
error_log /var/log/nginx/api_error.log debug;
access_log /var/log/nginx/api_access.log;
}

# 루트 경로 요청을 location-map-app으로 리다이렉트
location = / {
return 301 /location-map-app/;
}
}
4 changes: 3 additions & 1 deletion location-map-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
"eject": "react-scripts eject",
"predeploy": "npm run build",
"deploy": "gh-pages -d build"
},
"eslintConfig": {
"extends": [
Expand Down
4 changes: 2 additions & 2 deletions location-map-app/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
<meta property="og:title" content="토익 시험장 찾기" />
<meta property="og:description" content="전국 토익 시험장을 한눈에, 내 주변에서 가까운 곳을 찾아보세요." />
<meta property="og:type" content="website" />
<meta property="og:url" content="https://bevibing.duckdns.org/location-map-app/" />
<meta property="og:url" content="https://roundtable02.github.io/location-map-app" />
<meta property="og:image" content="%PUBLIC_URL%/img.png" /> <!-- 대표 이미지 있으면 좋아요 -->

<meta
Expand All @@ -51,7 +51,7 @@
"@context": "https://schema.org",
"@type": "WebSite",
"name": "토익 시험장 찾기",
"url": "https://bevibing.duckdns.org/location-map-app/",
"url": "https://roundtable02.github.io/location-map-app",
"description": "내 주변 토익 시험장을 찾고 싶다면 지금 검색해보세요.",
"inLanguage": "ko-KR"
}
Expand Down
2 changes: 1 addition & 1 deletion location-map-app/public/robots.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ User-agent: *
Disallow:
Allow: /

Sitemap: https://bevibing.duckdns.org/location-map-app/sitemap.xml
Sitemap: https://roundtable02.github.io/location-map-app/sitemap.xml
2 changes: 1 addition & 1 deletion location-map-app/public/sitemap.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@


<url>
<loc>https://bevibing.duckdns.org/location-map-app/</loc>
<loc>https://roundtable02.github.io/location-map-app/</loc>
<lastmod>2025-04-06T10:19:41+00:00</lastmod>
</url>

Expand Down
8 changes: 5 additions & 3 deletions location-map-app/src/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ export const LOCATION_FILTERS = ["서울", "경기", "인천", "부산", "대구
export const DEFAULT_MAP_CENTER: [number, number] = [37.5665, 126.9780]; // 서울 시청
export const DEFAULT_MAP_ZOOM = 13;

// API 엔드포인트 (선택 사항: 더 깔끔하게 관리)
// export const API_BASE_URL = 'https://api-h6nav77v5q-uc.a.run.app';
export const API_BASE_URL = './api';
// API 엔드포인트 - Docker 환경에서는 상대 경로를 사용하는 것이 좋음
export const API_BASE_URL = '/api';
export const TOEIC_SCHEDULE_ENDPOINT = `${API_BASE_URL}/toeic`;
export const TOEIC_CENTERS_ENDPOINT = `${API_BASE_URL}/toeic/centers`;

// Firestore 컬렉션 이름
export const FIRESTORE_COLLECTION_CENTERS = "toeicCenters";
17 changes: 0 additions & 17 deletions nginx/default.conf

This file was deleted.

Loading