my-nextjs-project/
├── docker/
│ └── nginx/
│ └── default.conf # Nginx 설정
├── .github/
│ └── workflows/
│ └── deploy.yml # GitHub Actions 배포 설정
├── Dockerfile # Next.js 앱 도커 이미지 설정
├── docker-compose.yml # 컨테이너 구성 설정
├── .dockerignore # 도커 빌드 제외 파일
├── next.config.js # Next.js 설정
└── src/ # 소스 코드
Nginx + Next.jsproxy_pass에 따라 요청을 Next.js 컨테이너의 3000포트로 전달 → Next.js(3000)💡 작성한 파일들의 작동 순서와 방식
1. github main 브랜치에 코드 푸시
2.github/workflows/deploy.yml- github actions가 이 파일을 읽고 배포 과정을 시작한다. (docker compose 설치, 환경변수파일.env생성, docker hub 로그인)
3.Dockerfile- github actions가 이 파일을 사용해 Next.js 앱 이미지 빌드
- builder 단계 : Node.js 환경에서 Next.js앱을 빌드
- runner 단계 : 빌드된 결과물만 가져와 실행 환경 구성
(빌드된 이미지는 docker hub에 푸시)
- github actions가 다음 파일들을 EC2로 전송
docker-compose.ymldocker/nginx/default.conf.env- EC2서버에서 배포 실행
docker hub 로그인 > 명령어: 이미지 가져오기-기존 컨테이너 중지-새 컨테이너 시작docker-compose.yml- EC2서버에서 이 파일을 읽고 컨테이너 설정
- nginx 서비스: 80/443 포트를 사용하며 외부 요청을 받음
- nextjs 서비스: docker hub에서 가져온 이미지를 실행하며 3000 포트를 사용
(두 서비스는 app_network로 연결)
docker/nginx/default.conf- nginx 설정 파일
어떤 요청을 어디로 전달할지 정의, SSL 인증서 설정, 해당 파일은 docker-compose.yml에서 볼륨으로 마운트됨
import { withSentryConfig } from "@sentry/nextjs";
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
output: "standalone", // Docker 컨테이너에서 Next.js 애플리케이션을 실행하기 위한 설정
experimental: {
turbo: {
rules: {
"*.svg": {
loaders: ["@svgr/webpack"],
as: "*.js",
},
},
},
},
webpack: (config) => {
// Add rule for SVG files
config.module.rules.push({
test: /\.svg$/,
use: ["@svgr/webpack", "url-loader"],
});
return config;
},
reactStrictMode: true,
};
export default withSentryConfig(nextConfig, {
// For all available options, see:
... sentry 설정
node_modules
.next
.git
.env*
# 프론트엔드용 서버 블록
server {
listen 80; # IPv4
listen [::]:80; # IPv6
# server_name mywareho.me;
location / {
proxy_pass http://next-app:3000; # docker-compose 서비스명과 일치
# Next.js의 WebSocket 기능 지원 설정 (+ hot reload 기능)
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
# 프록시 헤더 설정
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
# 성능 최적화 위한 gzip 압축
# gzip on;
# gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
}
}
# 백엔드용 서버 블록
node.js 공식, Node.js 릴리즈 일정표, Node.js 공식 Docker 이미지 정보 를 확인해보면 Node.js 22가 LTS 버전인 것을 확인할 수 있다.


# 빌드
# nodejs의 버전을 명시해주고, builder라는 별칭을 부여
FROM node:22-alpine3.21 AS builder
# work 디렉토리를 /app으로 설정
WORKDIR /app
# 패키지 파일 복사 및 설치
COPY package*.json package-lock.json ./
# npm 설치
RUN npm install
# 프로덕션 종속성만 설치
# RUN npm ci --only=production
COPY . .
# Next.js 빌드
RUN npm run build
# 실행
FROM node:22-alpine3.21 AS runner
WORKDIR /app
ENV NODE_ENV=production
# 필요한 파일만 복사
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/.next/server ./server
# Sentry source maps
COPY --from=builder /app/.next/server ./server
COPY --from=builder /app/.next/static ./.next/static
# 오픈할 포트 설정
EXPOSE 3000
CMD ["node", "server.js"]
version: "3"
services:
nginx:
image: nginx:alpine
ports:
- "80:80" # 호스트의 80포트를 컨테이너의 80포트와 매핑
volumes:
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf # Nginx 설정 파일 마운트
depends_on: # nextjs 서비스가 먼저 실행되어야 함
- nextjs
restart: always # 컨테이너 중단시 항상 재시작
nextjs:
build:
context: .
dockerfile: Dockerfile
expose:
- "3000" # 컨테이너 내부에서만 3000포트 접근 가능
restart: always
name: Deploy to EC2
# 워크플로우 트리거 조건 설정
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
# Sentry 환경 변수 설정
- name: Create Sentry environment file
run: |
echo "NEXT_PUBLIC_SENTRY_DSN=${{ secrets.NEXT_PUBLIC_SENTRY_DSN }}" >> .env
echo "SENTRY_ORG=jinii9" >> .env
echo "SENTRY_PROJECT=wms" >> .env
# EC2로 파일 복사
- name: Copy files to EC2
uses: appleboy/scp-action@master
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USERNAME }}
key: ${{ secrets.EC2_SSH_KEY }}
source: ".,!node_modules,!.next,!.git"
target: "/home/ubuntu/app"
# EC2에서 Docker Compose 실행
- name: Deploy to EC2
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USERNAME }}
key: ${{ secrets.EC2_SSH_KEY }}
script: |
cd /home/ubuntu/app
docker-compose down --rmi all --volumes
docker system prune -af
docker-compose up --build -d
name: Deploy to EC2
# 워크플로우 트리거 조건 설정
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
# 환경 변수 파일 생성
- name: Create environment file
run: |
# API URL (프로덕션용)
echo "NEXT_PUBLIC_BASE_URL=${{ secrets.NEXT_PUBLIC_BASE_URL }}" >> .env
# Auth Secret
echo "AUTH_SECRET=${{ secrets.AUTH_SECRET }}" >> .env
# API Mocking 설정
echo "NEXT_PUBLIC_API_MOCKING=disabled" >> .env
# Sentry 설정
echo "NEXT_PUBLIC_SENTRY_DSN=${{ secrets.NEXT_PUBLIC_SENTRY_DSN }}" >> .env
echo "NEXT_PUBLIC_SENTRY_AUTH_TOKEN=${{ secrets.NEXT_PUBLIC_SENTRY_AUTH_TOKEN }}" >> .env
# Sentry 환경 변수 설정
- name: Create Sentry environment file
run: |
echo "NEXT_PUBLIC_SENTRY_DSN=${{ secrets.NEXT_PUBLIC_SENTRY_DSN }}" >> .env
echo "SENTRY_ORG=jinii9" >> .env
echo "SENTRY_PROJECT=wms" >> .env
# EC2로 파일 복사
- name: Copy files to EC2
uses: appleboy/scp-action@master
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USERNAME }}
key: ${{ secrets.EC2_SSH_KEY }}
source: ".,!node_modules,!.next,!.git"
target: "/home/ubuntu/app"
# EC2에서 Docker Compose 실행
- name: Deploy to EC2
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USERNAME }}
key: ${{ secrets.EC2_SSH_KEY }}
script: |
cd /home/ubuntu/app
docker-compose down --rmi all --volumes
docker system prune -af
docker-compose up --build -d
EC2_HOST : EC2 퍼블릭 IPv4 주소
EC2_USERNAME : ubuntu
EC2_SSH_KEY : EC2 접속 위해 받은 private key인 .pem 파일

github action 수행 파일workflow의 ci/cd 파일에서 gitignore되는 env 파일을 만들기 위해, env 환경변수를 하나씩 코드에 입력해주는 방법 말고, 한번에 파일을 복사해주는 방법으로 바꿔줘야겠다. 하나씩 ci/cd 파일에 환경변수를 적어주고, github secret에 등록해줘야 하는 동작을 여러번 해야하는게 너무 귀찮다.
❗️ 로컬에서 NextAuth 사용할 때는 자동으로 NextAuth 실행에 필요한 토큰이 생성됐는데 배포하니까 해당 토큰들이 생기지 않아 CSRF 토큰 관련 에러를 발생시켰다.

[auth][error] MissingCSRF: CSRF token was missing during an action callback. Read more at https://errors.authjs.dev#missingcsrf
해결하기 위해 서버 로그를 확인해보자!
NextAuth가 호스트를 신뢰할 수 없다고 판단해서 발생하는 문제였다.
# 실시간 로그 확인
docker logs -f 컨테이너이름

🙆🏻♀️ NextAuth 설정에 trustHost 옵션을 추가해준다.
import { IUser } from "@/custom";
import NextAuth, { Session } from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";
import { JWT } from "next-auth/jwt";
export const {
handlers: { GET, POST },
auth,
signIn,
} = NextAuth({
trustHost: true, // ✅ 추가
secret: process.env.NEXTAUTH_SECRET,
pages: {
signIn: "/login",
},
callbacks: {
async jwt({ token, user }) {
if (user) {
const userData = user as IUser;
token.userId = userData.userId;
token.role = userData.role;
token.phoneNumber = userData.phoneNumber;
token.id = userData.id;
token.name = userData.name;
}
return token;
},
async session({ session, token }: { session: Session; token: JWT }) {
session.user = {
userId: token.userId,
name: token.name,
role: token.role,
phoneNumber: token.phoneNumber,
id: token.id,
};
console.log("-----------SESSION CALLBACK:", session);
return session;
},
},
providers: [
CredentialsProvider({
async authorize(credentials): Promise<IUser> {
// 로그인 페이지에서 백엔드 인증을 처리 완료 상태
// credentials의 정보를 그대로 반환
return credentials as unknown as IUser;
},
}),
],
});
❗️ 로컬에서는 잘만 연결되던 로그인 api가 배포되니까 에러가 발생하였다.
콘솔창에 api 요청 url의 환경변수 부분process.env.NEXT_PUBLIC_API_URL이 undefined로 뜨는 걸로 보아 프로덕션 환경에서 환경변수.env가 적용되지 않은 것 같아 실행중인 docker 컨테이너에서 환경변수를 확인해보았다.
# EC2 터미널
# 실행 중인 컨테이너에서 환경변수 확인
docker exec -it 컨테이너이름 env | grep NEXT
하지만 환경변수들은 잘 설정되어 있었다. -> Docker 환경 변수를 확인했을 때는 NEXT_PUBLIC_API_URL이 설정되어 있는데, 실제 애플리케이션에서 접근이 안되는 것으로 보인다.
❓ .env에는 환경변수가 많은데
NEXT_PUBLIC_API_URL환경변수만 에러가 나는 이유
NEXT_PUBLIC_API_URL은 클라이언트 사이드에서 사용되는 환경변수아다.
하지만 다른 환경변수들은 서버 사이드에서만 사용되는 환경변수이다.Next.js에서
NEXT_PUBLIC_접두사가 붙은 환경변수는 특별한 처리가 필요하다!
이 변수들은 빌드 시점에 애플리케이션에 포함되어야 하기. ㅐ문이다.
🙆🏻♀️ NEXT_PUBLIC_ 접두사가 붙은 환경변수는 특별한 처리를 해준다.
📁 docker-compose.yml
Docker 이미지 빌드 시점에 환경변수가 포함되도록 수정한다.
그리고 Dockerfile에서도 환경변수를 받을 수 있도록 수정한다.
name: Deploy to EC2
on:
push:
branches: [main]
workflow_dispatch: # 수동 실행을 위한 트리거 추가
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
# Docker Compose 설치
- name: Install Docker Compose
run: |
sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
docker-compose version
# 환경 변수 파일 생성
- name: Create environment file
run: |
echo "NEXT_PUBLIC_BASE_URL=${{ secrets.NEXT_PUBLIC_BASE_URL }}" >> .env
echo "NEXT_PUBLIC_API_URL=${{ secrets.NEXT_PUBLIC_API_URL }}" >> .env
echo "NEXTAUTH_SECRET=${{ secrets.NEXTAUTH_SECRET }}" >> .env
echo "NEXTAUTH_URL=${{ secrets.NEXTAUTH_URL }}" >> .env
# API Mocking 설정
echo "NEXT_PUBLIC_API_MOCKING=disabled" >> .env
# Sentry 설정
echo "NEXT_PUBLIC_SENTRY_DSN=${{ secrets.NEXT_PUBLIC_SENTRY_DSN }}" >> .env
echo "NEXT_PUBLIC_SENTRY_AUTH_TOKEN=${{ secrets.NEXT_PUBLIC_SENTRY_AUTH_TOKEN }}" >> .env
echo "SENTRY_ORG=jinii9" >> .env
echo "SENTRY_PROJECT=wms" >> .env
# Docker Hub 로그인
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
# Docker 이미지 빌드 및 푸시
- name: Build and push Docker image
uses: docker/build-push-action@v4
with:
context: .
push: true
build-args: | # ✅
NEXT_PUBLIC_API_URL=${{ secrets.NEXT_PUBLIC_API_URL }}
NEXT_PUBLIC_BASE_URL=${{ secrets.NEXT_PUBLIC_BASE_URL }}
tags: |
${{ secrets.DOCKERHUB_USERNAME }}/wms-frontend:latest
${{ secrets.DOCKERHUB_USERNAME }}/wms-frontend:${{ github.sha }}
# EC2로 필요한 파일들만 복사
- name: Copy deployment files to EC2
uses: appleboy/scp-action@master
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USERNAME }}
key: ${{ secrets.EC2_SSH_KEY }}
source: "docker-compose.yml,docker/nginx/default.conf,.env"
target: "/home/ubuntu/app"
# EC2에서 배포 실행
- name: Deploy to EC2
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USERNAME }}
key: ${{ secrets.EC2_SSH_KEY }}
script: |
echo "${{ secrets.DOCKERHUB_TOKEN }}" | sudo docker login -u "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin
cd /home/ubuntu/app
echo "DOCKERHUB_USERNAME=${{ secrets.DOCKERHUB_USERNAME }}" >> .env
sudo docker-compose pull
sudo docker-compose down
sudo docker-compose up -d
📁 Dokerfile
Dokerfile에서 환경변수가 빌드 시점에 주입되도록 수정해야 한다.
# 빌드
# nodejs의 버전을 명시해주고, builder라는 별칭을 부여
FROM node:22-alpine3.21 AS builder
# work 디렉토리를 /app으로 설정
WORKDIR /app
# ✅ 빌드 시점의 ARG 선언
ARG NEXT_PUBLIC_API_URL
ARG NEXT_PUBLIC_BASE_URL
# ✅ 빌드 환경에서 사용할 ENV 설정
ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL
ENV NEXT_PUBLIC_BASE_URL=$NEXT_PUBLIC_BASE_URL
# 패키지 파일 복사 및 설치
COPY package*.json package-lock.json ./
# npm 설치
RUN npm install
# 프로덕션 종속성만 설치
# RUN npm ci --only=production
COPY . .
# Next.js 빌드
# RUN npm run build
RUN npm run build --no-lint
# 실행
FROM node:22-alpine3.21 AS runner
WORKDIR /app
ENV NODE_ENV=production
# 런타임 환경변수 설정
ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL
ENV NEXT_PUBLIC_BASE_URL=$NEXT_PUBLIC_BASE_URL
# 필요한 파일만 복사
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/.next/server ./server
# Sentry source maps
COPY --from=builder /app/.next/server ./server
COPY --from=builder /app/.next/static ./.next/static
# 오픈할 포트 설정
EXPOSE 3000
CMD ["node", "server.js"]