
AI 시대에 맞추어 2주에 한 프로덕트를 만들어내는, 작지만 빠른 개발을 지향하는 프로젝트입니다.
리버스 프록시 서버를 구현하여 서버 도메인을 성공적으로 가릴 수 있었다.
Let's Encrypt도 함께 사용하여 HTTPS까지 적용하였다.
그런데 문제는, 리소스를 받아오는 과정에서 리버스 프록시를 거치기 때문에 처음 페이지에 접속하는 경우 4-5초 정도의 시간이 걸린다는 문제가 있었다.
문제점
지금 방식은 너무 느리다!
이번 포스팅에서 원하는 목표는 다음과 같다.
1. 속도를 개선할 것
2. Github Pages를 포기하되, 배포에 어려움은 없어야 한다.
3. 얇은 내 지갑을 지켜라!
해결방안
찾아보니 두 방식 정도로 좁혀지는 것 같았다.
- S3 + CloudFront로 배포한다
- EC2에 도커로 배포한다.
구체화
1번 방식이 간단한 것 같았으나,
을 고려하여 2번으로 결정하였다!
Github Main 브랜치에서 push 감지
-> Actions로 도커 이미지를 만들어 서버에 배포
이 단계로 진행하면 될 것 같다.
[ChatGPT와 함께 진행하였습니다.]
# 도커 설치
sudo apt update
sudo apt install -y docker.io
sudo systemctl enable docker
sudo systemctl start docker
# 나중을 위해 compose도 설치
sudo apt install -y docker-compose
이제 깃허브 레포지토리로 이동해서 설정 > Secrets > Actions

레포지토리 Secrets를 추가한다.
.env에 포함되어 있던 Google Map API Key랑
ec2에 접속하기 위해
EC2_HOST (IPv4 public)
EC2_USER ubuntu (EC2에 접속할 유저 이름)
EC2_SSH_KEY EC2 .pem 파일의 내용 전체
이렇게 3개의 Secret key를 추가했다.
이제 프로젝트 루트에 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
RUN npm run build
FROM nginx:alpine
COPY --from=build /app/build /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
도커 이미지를 만드는 데 필요한 파일들을 빌드하고, 정적 파일들만 이미지에 담아 저장한다.
이미지에는 nginx도 같이 올라가는데, 컨테이너 내부 80번 포트에서 실행된다. (React는 3000번)
루트 파일에 nginx 설정 파일을 추가한다.
# nginx.conf
server {
listen 80;
server_name bevibing.duckdns.org;
location /location-map-app/ {
root /usr/share/nginx/html;
index index.html;
try_files $uri $uri/ /location-map-app/index.html;
}
}
컨테이너 내부의 nginx는 80번에서 요청을 받아
정적 파일들이 들어있는 디렉토리에서 리소스를 반환한다.
.github/workflows/deploy.yml 을 생성한다.
name: Deploy React App to EC2 with Docker
on:
push:
branches: [ main ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Build React app
run: |
echo "REACT_APP_GOOGLE_MAPS_API_KEY=${{ secrets.REACT_APP_GOOGLE_MAPS_API_KEY }}" > .env
npm install
npm run build
- name: Build Docker image
run: docker build -t my-react-app --build-arg REACT_APP_GOOGLE_MAPS_API_KEY=${{ secrets.REACT_APP_GOOGLE_MAPS_API_KEY }} .
- name: Save private key
run: |
echo "${{ secrets.EC2_SSH_KEY }}" > private_key.pem
chmod 600 private_key.pem
- name: Save Docker image and send to EC2
run: |
docker save my-react-app | bzip2 | ssh -o StrictHostKeyChecking=no -i private_key.pem ${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }} 'bunzip2 | docker load'
- name: Run Docker container on EC2
run: |
ssh -o StrictHostKeyChecking=no -i private_key.pem ${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }} << 'EOF'
docker stop my-react-app || true
docker rm my-react-app || true
docker run -d --name my-react-app -p 3000:80 my-react-app
EOF
Main에 Push를 감지하면,
Actions에서 Secret key를 넘겨 도커 이미지를 빌드
SSH 접속용 Private key를 생성해
빌드한 이미지를 압축해서 EC2로 전송한 후 컨테이너를 띄운다.
맨 마지막 '-p 3000:80' 이 부분에서 컨테이너 외부의 3000번 포트를 컨테이너 내부 80번 포트와 연결한다는 점!!
이제 EC2 nginx를 다음과 같이 설정한다.
location /location-map-app/ {
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
이제 /location-map-app으로 접속하면,
리버스 프록시로 localhost:3000으로 전환되고, 3000번 포트에는 컨테이너의 nginx 동작하고 있다.
컨테이너 내부의 nginx가 정적 파일을 가져오는 것으로 흐름이 종료된다.
리버스 프록시를 사용하는 이유
이제 메인에 변경사항을 Push..!

deploy에 성공한다.
https://bevibing.duckdns.org/location-map-app/
이제 접속도 빠르게 진행된다!

Pages 브랜치를 None으로 바꿔 Github 퍼블리싱도 중단 완료!
bunzip2: command not found
ec2에 bunzip이 안 깔려 있어서 발생한 문제
sudo apt-get install -y bzip2
이걸로 해결
permission denied while trying to connect to the Docker daemon socket
Docker에 ubuntu 유저에 대한 권한을 주지 않아서 생긴 문제다..
sudo usermod -aG docker ubuntu
이걸로 해결!
처음의 목표 세 가지를 모두 지켜내었다!
속도 문제도 해결! 다음 이슈는 또 무엇이 있을까..? 😭