AWS EC2 인스턴스 구축 후 EC2 Docker 설치까지 마쳤다면, 이제 배포를 할 때가 왔다.
트러블 슈팅

GitHub Action 을 이용하여 Push 시 Master brach에 있는 프로젝트를 Docker 컨테이너를 이용해 AWS EC2 인스턴스에서 구동하게끔하는 자동화 파이프라인을 구축 할 예정이다.

우선 프로젝트 최상단에 Dockerfile을 생성해준다.
Next.js 애플리케이션을 Docker 컨테이너 안에서 실행하기 위한 설정.
# Node.js 20 버전 사용 (Next.js 지원 버전)
FROM node:20-alpine
# 명시적으로 /app 디렉토리 생성
RUN mkdir -p /app
WORKDIR /app
# package.json과 package-lock.json을 복사
COPY package*.json ./
# 의존성 설치
RUN npm install
# .env.local 파일 복사
COPY .env.local /app/.env.local
# .env.local 파일 확인
RUN ls -la /app && cat /app/.env.local
# 애플리케이션 파일 복사
COPY . .
# 애플리케이션 빌드
RUN npm run build
# 프로덕션 환경에서 서버 실행
CMD ["npm", "run", "start"]
# 컨테이너가 3000 포트를 리스닝하도록 설정
EXPOSE 3000
같은 위치에 .github/workflows/deploy.yml 생성해준다.
deploy.yml 은 코드가 merge 되었을 때 이를 감지하고 2차 동작을 하도록 하는 파일이다.
Docker Hub에 업로드 할 이미지 이름 : 자동으로 업로드하니 정하기만 하면 된다.
- 나의 경우 tst-fe-image 로 설정할 예정이다.
- seuo/tst-fe-image
환경 변수 : .env.local에 사용하는 환경 변수를 설정한다.
- NEXT_PUBLIC_SPRINGBOOT_URL=${{ secrets.NEXT_PUBLIC_SPRINGBOOT_URL }}
- NEXT_PUBLIC_S3_URL=${{ secrets.NEXT_PUBLIC_S3_URL }}
Docker Hub 로그인 정보
- secrets.DOCKER_USERNAME : Docker 계정
- secrets.DOCKER_PASSWORD : Docker 암호
서버 접속 정보 수정
- secrets.SERVER_HOST : FE 서버 IP 주소
- secrets.SERVER_USER : 서버 로그인 사용자 명
- secrets.PEM_KEY: 서버 pem 키 값
Git Seceret으로 값들을 관리한다.
이후에 .github/workflows/deploy.yml 생성 후
name: Deploy with Docker
on:
push:
branches:
- master
jobs:
# Build Job: Docker 이미지 빌드 및 푸시
build:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v3
- name: Create .env.local file from Secrets
run: |
echo "NEXT_PUBLIC_SPRINGBOOT_URL=${{ secrets.NEXT_PUBLIC_SPRINGBOOT_URL }}" >> .env.local
echo "NEXT_PUBLIC_S3_URL=${{ secrets.NEXT_PUBLIC_S3_URL }}" >> .env.local
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Log in to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and Push Docker Image
run: |
docker build --no-cache -t seuo/tst-fe-image:latest .
docker push seuo/tst-fe-image:latest
# Deploy Job: 빌드 후 배포
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- name: Deploy to Server
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.PEM_KEY }}
script: |
cd ~/tst-fe-deploy || mkdir ~/tst-fe-deploy && cd ~/tst-fe-deploy
# 🔥 기존 컨테이너 중복 방지용 정리 (tst-fe-image 형식에 맞게 수정)
docker stop tst-fe || true
docker rm tst-fe || true
docker stop nginx || true
docker rm nginx || true
docker stop goaccess || true
docker rm goaccess || true
# 최신 프론트엔드 이미지 pull
docker pull seuo/tst-fe-image:latest
# 🧾 docker-compose.yml 생성
cat <<EOF > docker-compose.yml
version: "3.8"
services:
frontend:
container_name: tst-fe
image: seuo/tst-fe-image:latest
restart: always
expose:
- "3000"
networks:
- frontend-net
nginx:
image: nginx:latest
container_name: nginx
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx-logs:/var/log/nginx
depends_on:
- frontend
networks:
- frontend-net
goaccess:
image: allinurl/goaccess
container_name: goaccess
environment:
- GOACCESS_ACCESS_LOG=/var/log/nginx/access.log
- GOACCESS_OUTPUT_FILE=/usr/share/nginx/html/report.html
ports:
- "7890:7890" # GoAccess 대시보드 포트
volumes:
- ./nginx-logs:/var/log/nginx
- ./goaccess-report:/usr/share/nginx/html
networks:
- frontend-net
depends_on:
- nginx
networks:
frontend-net:
driver: bridge
EOF
# 🛠️ nginx.conf 생성 (Next.js 3000 포트로 프록시)
cat <<EOF > nginx.conf
events {}
http {
access_log /var/log/nginx/access.log;
server {
listen 80;
location / {
proxy_pass http://frontend:3000; # IP 또는 도메인 주소로 수정
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_set_header X-Forwarded-Proto \$scheme;
}
}
}
EOF
docker compose down
docker compose up -d
이후 master 브랜치로 merge하면 감지되어 동작한다.
Actions에서 확인할 수 있다.


Dockerfile 생성
# 1단계: 빌드용 이미지
FROM gradle:7.6-jdk17 AS builder
WORKDIR /app
COPY . .
RUN gradle clean build -x test --no-daemon
# 2단계: 실행용 경량 이미지
FROM openjdk:17-alpine
WORKDIR /app
COPY --from=builder /app/build/libs/app.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-Xmx256m", "-Xms128m", "-XX:+UseSerialGC", "-jar", "app.jar"]
같은 위치에 docker-compose.yml 생성
1. API 서버
2. Redis
3. DB
를 하나의 EC2로 돌려야하기 때문에 Docker-composer로 순차적으로 구동되도록 설정 해준다.
version: '3.8'
volumes:
mysql-data:
services:
redis:
image: redis:alpine
container_name: redis
restart: unless-stopped
ports:
- "6379:6379"
mem_limit: 128m
cpus: 0.2
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
db:
image: mysql:8
container_name: mysql-db
restart: on-failure # 실패 시에만 재시작
ports:
- "3306:3306"
environment:
MYSQL_DATABASE: tstdb
MYSQL_USER: tessbro
MYSQL_PASSWORD: tess1234
MYSQL_ROOT_PASSWORD: 1234
volumes:
- mysql-data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
retries: 5
app:
image: seuo/tst-be-image
container_name: tst-be
restart: always
ports:
- "80:8080"
depends_on:
- db
- redis
environment:
SPRING_DATASOURCE_URL: jdbc:mysql://db:3306/tstdb
SPRING_DATASOURCE_USERNAME: tessbro
SPRING_DATASOURCE_PASSWORD: tess1234
SPRING_REDIS_HOST: redis
SPRING_REDIS_PORT: 6379
nginx:
image: nginx:latest
container_name: nginx-proxy
restart: unless-stopped
ports:
- "80:80"
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d:ro
depends_on:
- app
volumes:
mysql-data:
기입해야할 정보 체크
DB 접속 정보
secret.MYSQL_DATABASE : DB 이름
secret.MYSQL_USER : DB 사용자 이름
secret.MYSQL_PASSWORD : DB 비밀번호
secret.MYSQL_ROOT_PASSWORD : Root 비밀번호
Docker 이미지 이름
secret.DOCKER_USERNAME/[이미지 이름:나의 경우 tst-be-imgae]
secret.DOCKER_USERNAME
secret.DOCKER_PASSWORD
API -> DB 접속 정보
SPRING_DATASOURCE_URL : DB의 접속 주소
SPRING_DATASOURCE_USERNAME : DB에 접속할 때 사용할 사용자명
SPRING_DATASOURCE_PASSWORD : 사용자 계정의 비밀번호
./github/workflows/deploy.yml 생성
# github repository actions 페이지에 나타날 이름
name: Deploy with Docker
# event trigger
on:
push:
branches: [ "master" ]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
# JDK setting - github actions에서 사용할 JDK 설정 (프로젝트나 AWS의 java 버전과 달라도 무방)
- uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
# gradle caching - 빌드 시간 향상
- name: Gradle Cache
uses: actions/cache@v3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle
# 환경별 application.properties 파일 생성(1) - application.properties
- name: Make application.properties
run: |
mkdir -p src/main/resources
echo "${{ secrets.APP_PRO }}" > src/main/resources/application.properties
# 환경별 yml 파일 생성(1) - application-aws.yml
# 서버 <-> S3 기능 추가 시 활성화
# - name: Make application-aws.yml
# run: |
# mkdir -p src/main/resources
# echo "${{ secrets.YML_AWS }}" > src/main/resources/application-aws.yml
- name: Build project
run: ./gradlew build -x test
- name: Log in to DockerHub
run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
- name: Build & Push Docker image
run: |
docker build -t seuo/tst-be-image .
docker push seuo/tst-be-image:latest
- name: Deploy to EC2
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.USERNAME }}
key: ${{ secrets.PEM_KEY }}
script: |
mkdir -p ~/tst-deploy/nginx/conf.d && cd ~/tst-deploy
# 최신 이미지 가져오기
docker pull seuo/tst-be-image
# Nginx default.conf 생성
cat <<EONGINX > ~/tst-deploy/nginx/conf.d/default.conf
server {
listen 80;
location / {
proxy_pass http://app:8080;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
#proxy_set_header Origin http_origin;
proxy_connect_timeout 1200;
proxy_send_timeout 1200;
proxy_read_timeout 1200;
send_timeout 1200;
}
}
EONGINX
# docker-compose.yml 생성
cat <<EOF > docker-compose.yml
version: '3'
services:
redis:
image: redis:alpine
container_name: redis
restart: unless-stopped
ports:
- "6379:6379"
mem_limit: 128m
cpus: 0.2
db:
image: mysql:8
container_name: mysql-db
restart: unless-stopped
ports:
- "3306:3306"
environment:
MYSQL_DATABASE: tstdb
MYSQL_USER: tessbro
MYSQL_PASSWORD: tess1234
MYSQL_ROOT_PASSWORD: 1234
volumes:
- mysql-data:/var/lib/mysql
mem_limit: 512m
cpus: 0.3
app:
image: seuo/tst-be-image
container_name: tst-be
restart: unless-stopped
expose:
- "8080"
depends_on:
- db
- redis
environment:
SPRING_DATASOURCE_URL: jdbc:mysql://db:3306/tstdb
SPRING_DATASOURCE_USERNAME: tessbro
SPRING_DATASOURCE_PASSWORD: tess1234
SPRING_REDIS_HOST: redis
SPRING_REDIS_PORT: 6379
mem_limit: 768m
cpus: 0.4
nginx:
image: nginx:latest
container_name: nginx-proxy
restart: unless-stopped
ports:
- "80:80"
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d:ro
depends_on:
- app
volumes:
mysql-data:
EOF
docker compose down
docker compose up -d
기입해야할 정보 체크
application.properies
secrets.APP_PRO
Docker 계정
secrets.DOCKER_USERNAME
secrets.DOCKER_PASSWORD
EC2 서버 정보
secrets.SERVER_HOST : BE EC2 IP 주소
secrets.USERNAME : EC2 사용자 이름 보통 ubuntu
secrets.PEM_KEY : .pem키의 내용을 모두 복사한다.
기분 좋은 녹색 뱃지 ~
잘 부탁해 ~
내 프론트 FE2 주소로 접속하면

서비스가 구동되는 모습을 볼 수 있다.
다음 작업