안녕하세요. 스타트업에서 프론트엔드 개발을 맡고 있는 inseong입니다 😁
이번 글에서는 GitHub Actions 활용해 Next.js를 NHN Cloud에 자동 배포하는 방법을 정리해보려고 합니다.
이전에는 AWS 환경에서 S3와 CloudFront를 활용한 배포 자동화를 구성했었는데요. 이번에는 NHN Cloud + Nginx + Next.js
Static Export 환경에서 CI/CD를 구성하면서 AWS와는 어떤 점이 달랐는지도 함께 정리해보겠습니다.
배포 환경 구성도

Dev / Prod 브랜치 구성
▫️ 회사마다 브랜치 전략은 다를 수 있지만, 이 글에서는 개발 작업을 진행할 Dev 브랜치와 실제 배포를 위한 Prod 브랜치를 생성해 사용합니다.
▫️ Dev 브랜치에서 개발 및 테스트를 진행하고, 작업이 완료되면 변경 사항을 Prod 브랜치에 반영합니다.

Next.js Static Export 설정
React / Vite는 빌드 시 생성되는 dist/ 디렉터리를 Nginx에 바로 서빙할 수 있습니다.
Next는 기본적으로 .next/에 빌드되기 때문에, 정적 파일로 배포하려면 output : 'export' 설정을 통해 out/ 디렉터리를 생성해야 합니다.
▫️ NHN 서버에서 Node.js 서버를 상시 실행하는 방식이 아니라, Next.js의 정적 빌드 결과물을 Nginx가 직접 서빙하는 방식으로 배포합니다.
▫️ 따라서 next.config.ts에 output: 'export' 설정을 추가해 npm run build 실행 시 out/ 디렉터리가 생성되도록 구성합니다.
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
reactStrictMode: true,
output: 'export',
images: {
unoptimized: true,
},
}
export default nextConfig
NHN 서버에 프로젝트 배포
▫️ Next의 정적 배포 설정을 완료했다면, 이제 NHN Cloud 서버에 프로젝트를 배포합니다.
▫️ 이번 프로젝트에서는 NHN인스턴스에 SSH로 접속한 뒤 GitHub Repository를 Clone하고, 서버에서 직접 의존성 설치 및 빌드를 진행합니다.
▫️ 하나의 Cloud 서버에서 여러 프로젝트가 운영되고 있기 때문에 서버의 Node 버전을 전역으로 변경하지 않고 NVM을 사용해 프로젝트별 노드 버전을 관리합니다.
Nginx 파일 구성
▫️ /etc/nginx/sites-enabled 경로로 이동한 뒤, 해당 서비스에서 사용할 Nginx 설정 파일을 생성합니다.
# ① HTTP 요청을 HTTPS로 리다이렉트
server {
listen 80;
server_name dev.comfac.kr;
# HTTPS 인증서를 만든 경로
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
return 301 https://$host$request_uri;
}
# ② HTTPS 및 SSL 인증서 설정
server {
listen 443 ssl http2;
server_name dev.comfac.kr;
ssl_certificate /etc/letsencrypt/live/dev.comfac.kr/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/dev.comfac.kr/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
# ③ Next.js 빌드 결과물 지정
root 파일경로/out;
index index.html;
# ④ Next.js 정적 페이지 라우팅
location / {
try_files $uri $uri.html $uri/ =404;
}
}
가비아에서 도메인 연결
▫️ Nginx 설정까지 완료했다면, 이제 dev.comfac.kr이 NHN Cloud 서버를 바라보도록 DNS를 연결합니다.
▫️ 해당 메인 도메인의 DNS 관리로 들어가서 A타입의 서브도메인명, NHN 인스턴스의 아이피를 연결합니다.

HTTPS 적용
▫️ 도메인과 NHN Cloud 서버 연결이 완료되었다면, HTTPS 통신을 위해 SSL 인증서를 적용합니다.
▫️ 이번 프로젝트에서는 무료 SSL 인증서를 제공하는 Let's Encrypt와 Certbot을 사용했습니다.
1️⃣ Certbot 설치
sudo dnf install -y epel-release
sudo dnf install -y certbot
2️⃣ 인증서 검증에 사용할 경로 생성
sudo mkdir -p /var/www/certbot
3️⃣ Nginx 구성 파일
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
4️⃣ 설정 확인 및 인증서 발급
sudo nginx -t
sudo systemctl reload nginx
sudo certbot certonly \
--webroot \
-w /var/www/certbot \
-d dev.comfac.kr
5️⃣ 확인 및 갱신 테스트
curl -I "도메인 주소"
sudo certbot renew --dry-run
GitHub Actions로 자동 배포 구성
▫️ 지금까지는 NHN 서버에 직접 접속해 git pull, npm ci, npm run build를 실행하여 배포를 진행했습니다.
▫️ 이제부터는 Prod 브랜치에 Push 이벤트가 발생하면 GitHub Actions가 자동으로 실행되어 NHN Cloud 서버의 코드를 최신 상태로 동기화하고 빌드하도록 구성합니다.
처음 시도했던 방식
▫️ 처음에는 GitHub Actions의 GitHub-hosted Runner에서 SSH를 통해 NHN Cloud 서버에 접속하는 방식으로 자동 배포를 구성했습니다.
▫️이를 위해 Repository Secrets에 서버 접속 정보를 등록했습니다.
NHN_HOST
NHN_USER
NHN_SSH_KEY
NHN_KNOWN_HOSTS
ssh: connect to host *** port 22: Connection timed out
▫️ NHN Cloud의 보안 그룹을 확인해보니 22포트가 보안을 위해 회사 등 특정 IP에서만 접근할 수 있도록 /32로 제한되어 있었습니다.
▫️ GitHub-hosted Runner는 해당 허용 IP에 포함되지 않기 때문에 SSH 연결 자체가 NHN 보안그룹에서 차단되고 있었습니다.
해결 방향
▫️ GitHub Actions를 위해 SSH 포트를 모든 IP에 개방할 수도 있지만, 하나의 서버에서 여러 사내 서비스가 함께 운영되고 있기 때문에 보안상 해당 방법은 사용하지 않았습니다.
▫️ 대신 외부 GitHub Runner가 서버에 접속하는 구조가 아닌, NHN Cloud 서버에서 GitHub Actions 작업을 가져오는 Self-hosted Runner 방식으로 변경했습니다.
GitHub Runner 설치
mkdir actions-runner && cd actions-runner
Runner 패키지 다운로드
curl -o actions-runner-linux-x64-<VERSION>.tar.gz \
-L https://github.com/actions/runner/releases/download/<VERSION>/actions-runner-linux-x64-<VERSION>.tar.gz
tar xzf ./actions-runner-linux-x64-<VERSION>.tar.gz
깃허브 저장소 연결
./config.sh \
--url https://github.com/<OWNER>/<REPOSITORY> \
--token <RUNNER_TOKEN>
일회성 토큰 받는법
▫️Settings ➡️ Actions ➡️ Runners ➡️ New self-hosted runner
💡 Self-hosted Runner 사용 시 주의점
Self-hosted Runner는 GitHub Actions의 명령을 실제 서버 내부에서 실행합니다. 따라서 외부 사용자가 임의의 Workflow를 실행할 수 없도록 Repository 권한을 관리하고, 신뢰할 수 있는 브랜치의 배포 Workflow에서만 Runner를 사용하는 것이 좋습니다.

설정 완료 후 테스트
▫️ ./run.sh로 Runner 연결을 확인한 뒤, SSH 세션이 종료되어도 Runner가 계속 동작할 수 있도록 서비스로 등록합니다.
sudo ./svc.sh install
sudo ./svc.sh start
sudo ./svc.sh status
깃허브 워크플로우 작성
▫️ Prod 브랜치에 Push 이벤트가 발생하면 Self-hosted Runner가 작업을 수신하고, NHN Cloud 서버 내부에서 Prod 브랜치 동기화 → 의존성 설치 → Next.js 빌드 → out/ 갱신까지 자동으로 수행하도록 Workflow를 구성합니다.
▫️ Nginx는 이미 파일경로/out을 바라보고 있기 때문에 빌드가 완료되면 별도의 Nginx 재시작 없이 새로운 배포 결과가 바로 반영됩니다.
name: Deploy Comfac Homepage
on:
push:
branches:
- Prod
workflow_dispatch:
permissions:
contents: read
concurrency:
group: comfac-homepage-dev-deployment
cancel-in-progress: true
jobs:
deploy:
name: Deploy static site to NHN Cloud
runs-on:
- self-hosted
- Linux
- X64
- comfac-homepage
timeout-minutes: 30
steps:
- name: Sync Prod and build the static site
run: |
set -euo pipefail
readonly project_dir='파일경로'
readonly expected_node_version='v24.16.0'
cd "$project_dir"
git fetch origin
if git show-ref --verify --quiet refs/heads/Prod; then
git switch Prod
else
git switch --track -c Prod origin/Prod
fi
git reset --hard origin/Prod
export NVM_DIR="$HOME/.nvm"
if [ ! -s "$NVM_DIR/nvm.sh" ]; then
echo "nvm is not installed for the deployment user." >&2
exit 1
fi
. "$NVM_DIR/nvm.sh"
nvm use
actual_node_version="$(node -v)"
if [ "$actual_node_version" != "$expected_node_version" ]; then
echo "Expected Node $expected_node_version, but found $actual_node_version." >&2
exit 1
fi
node -v
npm -v
nice -n 10 npm ci
nice -n 10 npm run build
test -f out/index.html
test -d out/_next
deployed_sha="$(git rev-parse HEAD)"
{
echo "## 컴팩 홈페이지 개발 서버 배포 완료"
echo
echo "- Branch: Prod"
echo "- Commit: \`$deployed_sha\`"
echo "- Target: https://dev.example.com
echo "- Node: 24.16.0"
} >> "$GITHUB_STEP_SUMMARY"

Next 프로젝트를 NHN + Nginx 환경에 배포하고, GitHub Actions를 이용해 자동 배포 환경을 구성하는 과정을 정리해보았습니다.