n8n으로 PR 자동 리뷰 플로우 구축하기
- JavaScript
- n8n
⭐ n8n 이란?
AI Workflow Automation Platform - n8n

n8n 은 노드 기반 워크플로우 자동화 플랫폼 이다.
사람이 직접 코딩을 할 필요 없이 단순한 시각적 인터페이스를 통해 다양한 서비스와 애플리케이션등을 연결해 자동화된 워크플로우를 구축할 수 있게 해준다.
🤔 그래서 그게 뭔데?
더 쉽게 이해하자면 여러 서비스와 API 를 묶어서 자동으로 일을 처리해주는 워크플로우 자동와 플랫폼이다.
n8n은 No 코드, Low 코드 방식이라 단순히 드래그 앤 드롭으로 노드 들을 이어서 자동화 플로우(워크플로우)를 만들 수 있는 도구다.
이는 오픈소스 기반이며 자체 서버에 설치해서 사용할 수 있다는 특징이 존재한다. 그러다보니 회사 내부용 자동화나 데이터 보안이 중요한 환경에서 많이 사용된다.
🤔 그래서 어떤 식으로 쓰이는데?
n8n은 Gmail, Slack, Notion, DB , Webhook, 크론 스케쥴러 등등 수백 개의 통합 노드를 제공해서 이벤트 -> 처리 -> 알림/저장과 같은 플로우를 쉽게 만들 수 있다!
예를 들어서 어떤 설문 폼에 응답이 들어오면 이걸 내용을 정리해서 Notion 페이지를 생성하고 Slack에 알림 보내기와 같은 반복 업무를 코드 없이 자동화하는 용도로 쓰인다.
📌 주요 특징
n8n의 주요 특징을 짚고 넘어가보자.
시작적 워크플로우 편집기
n8n은 따로 코드를 작성할 필요 없이 드래그 엔 드롭 방식으로 노드를 연결해서 복잡한 자동화 로직을 구현할 수 있다.
다양한 통합 노드
n8n은 500개 이상의 다양한 애플리케이션 서비스와 연동할 수 있는 기본 노드를 제공한다.
유연한 확장성
n8n은 JavaScript 코드를 사용해 직접 커스텀 노드를 개발하거나 기존 노드의 기능을 확장할 수 있어 거의 모든 자동화 시나리오에 대응할 수 있다.
셀프 호스팅 지원
n8n은 클라우드 버전 외에 자체 서버나 데스트탑에 설치할 수 있는 커뮤니티 버전(무료) 를 제공하여 보안을 강화할 수 있다.
매우 강력한 데이터 처리
여러 단계의 데이터 변환 흐름, 분기점, 병합, 반복 등 복잡한 작업 흐름을 손쉽게 처리할 수 있다.
📌 핵심 개념
n8n의 주요 개념과 핵심 단어들을 알아보자.
- 워크플로우 (Workflow)
- 자동화 작업의 전체적인 흐름을 정의하는 캔버스
- 하나 이상의
노드(Node)들로 구성되며 해당 노드들이 각각 연결, 특정 작업을 순차적으로 혹은 조건에 따라 수행한다.
- 노드 (Nodes)
- 워크플로우를 구성하는 가장 기본적인 단위
- 각 노드는 특정 기능(데이터 불러오기, 읽기, 쓰기, API 호출, 이메일 전송, 저장 등)을 수행한다.
- 노드는 크게 3가지 종류로 나뉜다.
- 트리거 노드(Trigger Nodes) : 워크플로우를 시작시키는 루트 노드. 특정 시간에 실행시키거나 웹훅을 수신하는 등의
트리거를 정의한다. - 일반 노드(Regular Nodes) : 데이터를 가져오거나, 변환하거나, 특정 작업을 수행하는 노드이다.
- 코어 노드(Core nodes) : 분기(IF), 병합(Merge) 등 Workflow의 흐름을 제어하는 노드이다.
- 연결 (Connections)
- 노드와 노드 사이를 이어주는 선이다.
- 특정 데이터가 한 노드에서 다음 노드로
어떻게전달될지를 결정한다.
- 자격 증명 (Credentials)
- API 키, 사용자 이름, 비밀번호 등 외부 서비스에 접근하기 위한 인증 정보를 안전하게 저장하고 관리하는 기능이다.
- 한 번 저장된 자격 증명은
여러 워크플로우에서 재사용할 수 있다.
- 실행 (Executions)
- 워크플로우가 트리거되어 실제로
실행된 기록을 의미한다. - 실행 로그를 통해 각 노드가 어떻게 동작했는지, 데이터가 어떻게 변환되었는지를 확인할 수 있어 디버깅에 매우 유용하다.
🤔 Zipar, Make와 비교하여 n8n이 강력한 이유는 아래 wikidocs를 참고해보자.
설명 Good
01-01 왜 n8n인가 (타 툴과 비교) - n8n 노코드 자동화 한글 가이드북
01-01 왜 n8n인가 (타 툴과 비교) - n8n 노코드 자동화 한글 가이드북
📌 n8n Expressions (n8n 표현식 문법)
Static”안녕하세요 대현님!” →Dynamic”안녕하세요 {{user_name}}님!”
n8n을 원활하게 사용하기 위해선 표현식 문법을 짚고 넘어가야한다.
n8n Expressions는 워크플로우 실행 중 생성된 데이터를 동적으로 참조하기 위한 문법이다.
n8n에서 각 노드는 실행 결과를 JSON 형태의 데이터(items)로 다음 노드에 전달하는데표현식 문법을 사용하면 이전 노드의 출력값을 읽어와서 URL, 파라미터, 조건식, 메시지 등에 동적으로 삽입할 수 있다.
🤔 왜 필요한거지?
→ n8n의 워크플로우는 대부분 고정된 값 이 아닌 Webhook으로 들어온 요청 값이나 API 응답 값, 이전 단계에서 계산된 값으로 구성되기 때문에 표현식이 없다면 항상 같은 URL, 같은 메시지, 같은 파라미터만 사용해야하는 상황이 발생한다.
→ 이러면 의도했던 자동화 시스템을 구축할 수 없기에 반드시 사용해야한다.
간단하게 기본 문법과 문자열 처리, 조건문 처리만 알고 넘어가자. (더 자세한 건 AI에게 물어보면 매우 쉽다.)
// 기본 문법
{{ $json.fieldName }} // 현재 노드 데이터
{{ $('노드명').first().json }} // 특정 노드 데이터
// 문자열 처리
{{ $json.firstName + ' ' + $json.lastName }} // 문자열 결합
{{ `안녕하세요 ${$json.name}님!` }} // 템플릿 리터럴
// 조건부 처리
{{ $json.status === 'active' ? '활성화' : '비활성화' }} // 삼항 연산자
{{ $json.user?.email || 'daehyeon@test.com' }} // 옵셔널 체이닝
💁 실습
목표
⭐ n8n을 통해 Github API 자동 리뷰 워크플로우를 구축하기
→ n8n 클라우드가 아닌 AWS(Docker) EC2에 띄워보자.
🤔 왜 EC2?
순수 학습용과 남은 3주 동안의 PR 리뷰를 진행하고 싶기에 따로 과금 없이 워크플로우를 구축하고 싶었다.
AWS LightSail, Elastic Beanstalk, ECS/EKS도 고려했지만 이러한 요소들은 오버엔지니어링이거나 n8n Cloud를 사용하는게 더 나을 것 같다는 생각이 들었다.
따라서 Docker와 docker-compose 자유도가 100%라고 판단되는 EC2로 구축해보고자 했다.
n8n에 좀 익숙해지면 그때 lightsali이나 gcp에도 적용해보자. (일단 지금 당장은 EC2가 훨씬 익숙하기 때문이기도 하다..ㅎㅎ)
우선, 세팅한 EC2 인스턴스(우분투)에 Docker를 설치하자.
# https 관련 패키지 설치
## apt-transport-https : HTTPS를 통해 패키지 다운로드하기 위함. (Docker의 공식 Repo는 HTTPS)
## ca-certificates : SSL 인증서 관리 패키지 (Docker 공식 저장소의 인증서 신뢰)
## curl : URL 데이터 가져오기 (Docker GPG키를 다운로드하기 위함)
## * Docker GPG란? : 패키지의 출처와 무결성을 보장하기 위한 보안 서명 도구)
## software-properties-common : 저장소 관리를 위한 유틸리티 (Docker 저장소를 우분투에 추가하기)
sudo apt install apt-transport-https ca-certificates curl software-properties-common
# docker repository 접근을 위한 GPG Key 설정
## curl -fsSL : Docker GPG 키 다운로드 (f : 실패 시 메시지 표시, s :출력 메시지 숨김, L : 리다이렉트를 따라가서 다운로드)
## sudo gpg --dearmor : GPG 키를 바이너리 형식으로 변환
## -o /usr/share/keyrings/docker-archive-keyring.gpg : GPG 키 저장 경로
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
# docker repository 등록
## dpkg --print-architecture : AMD64, ARM64와 같은 현재 시스템의 아키텍처 적용
## lsb_release -cs : 현재 우분투 배포판의 코드 이름 출력
## signed-by : Docker GPG 키 지정
## 그 후 나머지는 Docker 저장소 URL에 해당 파일을 추가하겠다는 의미.
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# apt 업데이트
sudo apt update
# Docker 설치
sudo apt install -y docker-ce
docker --version
## Docker version 29.1.5, build 0e6fee6
그 후 Docker Compose를 설치하자.
# Docker Compose 최신 버전 확인
## curl -s : Github API를 호출해 Docker Compose의 최신 릴리즈를 가져오고 출력을 간결하게 진행
## grep tag_name : 최신 릴리즈의 버전 태그 출력 (ex: v1.23.4)
curl -s https://api.github.com/repos/docker/compose/releases/latest | grep tag_name
## "tag_name": "v5.0.1",
# Docker Compose 설치
## curl -L : 지정된 URL을 다운로드하며 동시에 리다이렉션을 따라간다.
## $(uname -s) : 현재 OS를 가져온다.
## $(uname -m) : 현재 아키텍처를 가져온다.
## /usr/local/bin/docker-compose : Docker Compose 바이너리를 지정할 경오
sudo curl -L "https://github.com/docker/compose/releases/download/v2.32.4/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
# 실행 권한 부여
sudo chmod +x /usr/local/bin/docker-compose
# 확인
docker compose version
## Docker Compose version v5.0.1
2. Docker Compose 파일 작성
인스턴스에 적당한 폴더를 하나 생성한 후 아래와 같이 docker-compose.yml 파일을 작성한다.
version: '3.8'
services:
n8n:
image: n8nio/n8n
restart: always
ports:
- "5678:5678"
environment:
- DB_TYPE=postgresdb
- DB_POSTGRESDB_HOST=postgresql
- DB_POSTGRESDB_PORT=xxxx
- DB_POSTGRESDB_DATABASE=${POSTGRES_DB}
- DB_POSTGRESDB_USER=${POSTGRES_USER}
- DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}
- N8N_BASIC_AUTH_ACTIVE=true
- N8N_BASIC_AUTH_USER=${N8N_USER}
- N8N_BASIC_AUTH_PASSWORD=${N8N_PASSWORD}
- N8N_EDITOR_BASE_URL=http://<EC2_PUBLIC_IP>:5678
- WEBHOOK_URL=http://<EC2_PUBLIC_IP>:5678
- GENERIC_TIMEZONE=Asia/Seoul
volumes:
- ./n8n_data:/home/node/.n8n
depends_on:
- postgresql
postgresql:
image: postgres:13
restart: always
environment:
- POSTGRES_DB=${POSTGRES_DB}
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
volumes:
- ./pg_data:/var/lib/postgresql/data
이때 시간대(GENERIC_TIMEZONE)를 Asia/Seoul로 설정하였다.
실제 배포 환경에선 Nginx를 앞단에 두고 HTTPS만 열어서 n8n의 포트는 가급적 숨겨야한다.
🤔 이때, 왜
PostgreSQL을 설정하지?
→ n8n은 기본적으로 SQLite를 사용하지만 프로덕션 환경에서는 공식적으로PostgreSQL을 사용할 것을 권장한다.
→ n8n을 사용하면서 발생되는 모든 데이터는 PostgreSQL에 저장되며, 그 대상은 아래와 같다.
- Workflows : n8n 노드 구성과 설정 정보
- Executions : 각 워크플로우의 실행 결과, 입/출력 데이터
- Credentials : 외부 서비스 API 키 등
- 사용자 및 권한 : 멀티유저 환경에서의 접근 제어
→ n8n은 기본적으로 SQLite를 사용하지만 프로덕션 환경에서는 공식적으로 PostgreSQL을 사용할 것을 권장한다.
→ n8n을 사용하면서 발생되는 모든 데이터는 PostgreSQL에 저장되며, 그 대상은 아래와 같다.
- Workflows : n8n 노드 구성과 설정 정보
- Executions : 각 워크플로우의 실행 결과, 입/출력 데이터
- Credentials : 외부 서비스 API 키 등
- 사용자 및 권한 : 멀티유저 환경에서의 접근 제어
3. 환경 변수 파일 설정 (.env )
n8n 설정을 위해 환경 변수 .env를 하나 생성한 뒤 아래 내용을 작성한다.
# PostgreSQL 데이터베이스 설정
POSTGRES_DB=n8n
POSTGRES_USER=n8n_user
POSTGRES_PASSWORD=요기에 비밀번호 입력
# n8n 인증 설정
N8N_USER=admin
N8N_PASSWORD=요기에 비밀번호 입력
4. 서비스 실행
작성한 Docker Compose를 사용해서 n8n과 PostgreSQL를 백그라운드로 실행하자.
# 서비스 시작 (백그라운드로)
docker compose up -d
# 실행 상태 확인
docker compose ps
# 로그 확인
docker compose logs n8n
# 정상적으로 실행되면 ps 명령어 입력 시 다음과 같이 출력된다.
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
n8n-docker-n8n-1 n8nio/n8n "tini -- /docker-ent…" n8n 40 seconds ago Up 1 second 0.0.0.0:5678->5678/tcp, [::]:5678->5678/tcp
n8n-docker-postgresql-1 postgres:13 "docker-entrypoint.s…" postgresql 40 seconds ago Up 39 seconds 5432/tcp
5. 초기 설정 및 접속
정상적으로 실행이 되었다면 브라우저에서 http://{localhost 혹은 지정한 IP}:5678 로 접속해서 n8n에 로그인한다.

그 후 회원가입을 진행한다. (.env에 설정한 정보와는 별개이다.)

위와 같이 뜨면 성공!
6. Github 연결 (트리거)
Pull Request가 올라올 시 이를 AI가 자동 리뷰 해주는 것을 구현하기 위해선, Github 노드부터 설정할 필요가 있다.
PR이 올라온 걸 감지하게 되는 워크플로우 세팅이니 일종의 트리거이자 루트 노드 로 활용한다.
노드 세팅을 하기 전 Github Credential 설정을 진행하자.

Access Token 방식이나 OAuth2 토큰 인증 방식 둘 중 하나를 통해 계정 인증을 진행하자.
그 후 테스트로 생성한 저장소와 연결한다. (어느정도 세팅이 끝나면 실제 프로젝트에 적용할 예정이다.)


저장을 하면 workflow에 트리거 노드가 하나 생겨있는 것을 볼 수 있다.
이제 이 Github 트리거 노드는 Github에 PR 이벤트가 발생하면 n8n 워크플로우를 실행 하는 역할이다.
7. PR 이벤트 분기 처리하기
Github 트리거는 PR과 관련된 이벤트를 여러 종류로 받아온다.
예를 들어 PR이 열리거나(opened), 동기화(synchronize, 예: push로 커밋을 새롭게 올렸을 때), 다시 여는(reopened) 등 다양한 케이스가 들어올 수 있다.
따라서 AI가 PR에 리뷰를 어떤 시점에 달아줄 지 정하지 않으면 PR 본문을 수정하거나, 라벨 변경과 같은 이벤트에도 불필요하게 리뷰가 달릴 수 있다.
따라서, PR이 처음 생성(opened) 될 때만 워크플로우가 진행되도록 처리하자.

8. Pull Request 변경사항 추적하기
팀원이 Pull Request를 새로 생성하게 되면 해당 PR에서 변경된 파일 목록과 변경 사항(diff) 을 추출해야한다.
그래야 AI가 해당 PR에 대한 올바른 코드 리뷰 를 진행할 수 있을 것이다.
따라서 Github Rest API 문서를 통해 적합한 엔드포인트를 연결해보자.
끌어오기 요청에 대한 REST API 엔드포인트 - GitHub 문서
아래 노드를 설정할 때 사용한 엔드포인트는 다음과 같다.
# GitHub CLI api
# https://cli.github.com/manual/gh_api
gh api \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
/repos/OWNER/REPO/pulls/PULL_NUMBER/files
# GET /repos/{owner}/{repo}/pulls/{pull_number}/files

https://api.github.com/repos/{{$json.body.sender.login}}/{{$json.body.repository.name}}/pulls/{{$json.body.number}}/files
이때, PR 이벤트 payload 값에 따라 매번 owner, repo, pr_number 가 달라지므로 n8n 표현식을 사용해 URL을 동적으로 구성시키자.
또한 Github API 호출을 위해 Authentication을 클릭해서 Predefined Credential Type을 선택한 뒤 인증 타입을 Github 트리거 노드 설정할 때 사용한 계정을 선택한다.
9. 변경 사항 기반 AI 리뷰 프롬프트 생성하기 (Code in JavaScript Node)
이전 HTTP Request 노드에서 가져온 PR의 파일 정보는 JSON 형태이다.
하지만 LLM은 자연어 diff에 더 강하므로 Code 노드에서 LLM 프롬프트 스타일로 가공할 필요가 있다.

위처럼 Mode와 사용하고자 언어를 선택한 뒤 아래와 같이 코드를 작성한다.
하단 JavaScript 코드는 AI와 함께 작성하였다.
JavaScript 코드
/**
* Aggregate GitHub PR file diffs into a single LLM prompt
* Optimized for senior iOS code review
*/
const files = $input.all().map(item => item.json);
let diffs = '';
for (const file of files) {
// Skip files without patch (binary files, etc.)
if (!file.patch) continue;
diffs += `### File: ${file.filename}\n\n`;
// Prevent markdown breakage in LLM prompt
const safePatch = file.patch.replace(/```/g, "'''");
diffs += "```diff\n";
diffs += safePatch;
diffs += "\n```\n\n";
diffs += "---\n\n";
}
const userMessage = `
You are a senior iOS engineer with extensive experience reviewing production-level Swift code.
You are conducting a first-pass Pull Request code review.
Your primary goal is to catch human errors, oversights, and risky code before they reach production.
This review is intended as a PRE-MERGE SAFETY CHECK, not an architectural redesign or stylistic critique.
────────────────────────────────────
REVIEW SCOPE
────────────────────────────────────
Focus on identifying realistic mistakes and risks that commonly occur in iOS development, including but not limited to:
1. Human errors & oversights
- Typos, incorrect logic conditions, or copy-paste mistakes
- Variables, functions, or properties that are declared but never used
- Code paths that are unreachable or unintentionally always executed
- Mistakes caused by refactoring (renamed symbols, outdated references)
2. API usage correctness
- Usage of deprecated iOS / Swift / SwiftUI APIs or modifiers
- Incorrect or unsafe API assumptions (lifecycle, threading, availability)
- APIs used in a way that could lead to crashes or undefined behavior
- Misuse of SwiftUI modifiers that may not behave as intended
3. State & side-effect safety (lightweight check)
- State updates that appear inconsistent or incomplete
- Side effects that may accidentally execute multiple times
- Logic that could lead to duplicated work or unintended triggers
- Missing handling for failure or unexpected states
4. Concurrency & lifecycle risks
- async/await, Task, or GCD usage that may cause race conditions
- Missing cancellation or cleanup for long-lived tasks
- UI-related logic potentially executed off the main thread
- Objects or tasks that may unintentionally outlive their intended scope
5. Stability & edge cases
- Errors being silently ignored
- Optional handling that could lead to crashes
- Boundary conditions not explicitly handled
- Code that behaves correctly only under ideal assumptions
⚠️ Explicitly EXCLUDE the following from your review:
- Code formatting or SwiftLint-related comments
- Pure stylistic preferences or subjective naming opinions
- Large-scale refactoring or architectural redesign suggestions
- Debug-only or clearly temporary code
────────────────────────────────────
OUTPUT FORMAT (MANDATORY)
────────────────────────────────────
Produce a structured technical review report using the following format:
### 📄 PR Code Review Report
#### 🔍 Summary
- Overall assessment of potential risks or safety concerns
#### ⚠️ Key Issues
- Concrete problems that could realistically cause bugs, crashes, or unintended behavior
#### 🛠 Improvement Suggestions
- Non-critical improvements that increase robustness or clarity
#### ✅ Positive Observations
- Correct or careful implementations worth acknowledging
────────────────────────────────────
REVIEW RULES
────────────────────────────────────
- Review changes file by file, but do NOT mention file names
- Do NOT quote or repeat the code
- Write only concrete, actionable feedback
- Skip files without meaningful changes
- Do NOT explain the review process itself
- Prioritize issues that are likely to matter in real production scenarios
────────────────────────────────────
CODE CHANGES
────────────────────────────────────
${diffs}
Write the entire review in Korean.
`;
return [
{
json: {
user_message: userMessage.trim()
}
}
];
그 후 누적된 diff를 마크다운 형태로 변환하여 해당 프롬프트 전체 텍스트를 user_message 필드에 담아 다음 노드(AI Agent)로 전달한다.
이때, patch가 없는 파일. 즉 변경 사항이 없는 파일은 자동으로 스킵되며 diff 안에 ``` 가 포함되면 마크다운 블록이 깨질 수 있으므로 "'''"로 치환하였다.
10. Pull Request 코드 리뷰 생성하기 (AI Agent Node)
이제 Code 노드 에서 만든 user_message를 실제 LLM에게 전달하여 PR 코드 리뷰를 생성한다.

프롬프트 필드에 json.user_message 를 명시하여 이전 노드에서 생성한 프롬프트를 그대로 전달한다.
이때, AI Agent 노드는 단순히 “모델을 호출하는 노드”가 아닌 LLM을 활용한 작업을 하나의 에이전트로 실행하기 위한 컨테이너 역할을 수행한다.
따라서 아래와 같이 3개의 연결 포트를 제공한다.

현재 구축된 워크플로우에서는 Chat Model만 연결하고 Memory와 Tool은 비워둔 상태로 운영했다.
각 포트의 역할은 아래와 같다.
- Chat Model (필수)
→ AI가 실제로 결과를 생성하기 위해선 LLM 모델이 반드시 필요하다.
- 따라서 현재 워크플로우엔
Google Gemini Chat Model 노드를 연결했다. - Memory (선택)
→ Memory는 AI Agent가 이전 대화, 이전 실행에서 나온 정보를 기억한 뒤 다음 응답에 반영하도록 한다.
- 예를 들어 팀 코딩 컨벤션, 요구사항을 매번 넣지 않고 메모리에 저장해 반복적으로 사용할 수 있다.
- 현재 나의 경우 PR마다 입력값(diff)가 완전히 다르고 PR 기준 1회성 리뷰가 목적이기 때문에 제외했다.
- Tool (선택)
→ Tool은 AI Agent가 필요할 때 외부 기능이나 API를 직접 호출할 수 있도록 연결하는 기능이다.
- Github API를 추가적으로 호출해서 PR 상세나 코맨트, 커밋 목록을 조회하거나 Slack으로 결과를 보내기 등.. 다양한 액션을 추가적으로 수행시킬 때 사용한다.
- 더 고도화를 시키면
Tool포트를 사용해서 Notion에 명시된 팀 코드 컨벤션 스타일을 자동으로 로드하여팀 규칙 기반 리뷰를 수행시킬 수 있을 것이다.
이때 Chat Model엔 Google Gemini Chat Model 노드를 연결했으며
모델은 gemini-2.5-flash 를 사용했다.
2.5 flash 모델을 사용한 이유는 응답 속도가 빠르고 무엇보다 무료 여서 채택했다.
(여유 자금이 생긴다면 pro 계열이나 다른 LLM 모델도 사용할 것이다.)
11. AI 코드 리뷰를 PR에 게시하기
마지막으로 AI가 생성한 리뷰를 실제 Pull Request에 게시하기 위해 Create a Review Node를 사용한다.
해당 노드는 Github 노드의 Review 리소스에서 Create 작업을 지정했다.

이때, Body 필드에 {{ $json.output }} 을 명시했다.
AI Agent 출력 필드가 워크플로우 설정에 따라 output 이나 text, content 등으로 필드값이 달라질 수 있으므로 유의하자.
⭐ 12. 최종 Publish
최종적으로 구축한 AI 자동리뷰 파이프라인은 아래와 같다.

현재 n8n 워크스페이스는 로컬 호스트 기반 EC2에 배포중이다.
이를 통해 프로젝트에서 효율적으로 사용할 수 있다.
실제 리뷰가 달린 대표적인 PR은 아래와 같다.
https://github.com/boostcampwm2025/iOS03-dolAwang/pull/220
https://github.com/boostcampwm2025/iOS03-dolAwang/pull/287

🧠 고도화
1. LLM 모델 호출이 실패했을 때 재시도 요청하기
아래는 Goolgle Gemini Chat Model 노드의 Setting 탭이다.

AI 자동 리뷰는 본질적으로 외부 API (LLM)에 의존하는 구조이다.
그러다보니 Gemini API에 일시적으로 오류가 발생하거나,
사람이 많이 몰리는 시간대거나,
네트워크가 순간적으로 끊기거나,
rate limit가 초과되었을 때
외부 LLM API를 호출하지 못하여 코드 리뷰가 진행되지 않는 경우가 존재할 것이다.
따라서 Retry On Fail 옵션을 활성화하여 안정성을 확보시킨다.
재시도 횟수는 보편적인 3회 로 설정하고, 그 간격은 1초로 지정한다.
이때, On Error 를 Continue 로 설정한 이유는 워크플로우의 유연성을 확보하기 위해서다.
만약, 해당 필드를 Stop Workflow 로 설정한다면 모델 호출 실패 시 전체 워크플로우가 멈추고 다시 실행되어 불필요한 HTTP 요청 등 리소스가 크게 낭비 될 것이다.
따라서 Continue로 설정하여 LLM 호출이 실패해도 다음 노드에서 처리가 가능하도록 지정했다.
(하지만, 현재 워크플로에는 AI 리뷰가 실패해도 추가적인 액션이 실행되지는 않는다..)
🚧 트러블 슈팅
1. permission denied while trying to connect to the Docker daemon socket
docker compose 세팅도 하고 환경 변수 세팅도 끝난 후 docker compose up -d를 통해 백그라운드로 도커를 실행시키려 했지만 다음과 같은 오류가 발생하였다.
unable to get image 'n8nio/n8n': permission denied while trying to connect to the Docker daemon socket...
이는 현재 사용자가 Docker 데몬에 접근할 수 있는 권한이 없다는 의미이다.
현재 나는 EC2 인스턴스로 접속해서 관리하고 있기 때문에 내 사용자 정보를 docker 그룹에 추가하였다.
sudo usermod -aG docker {user_name}
그 후 SSH 세션을 갱신하거나 즉시 적용한다.
# 방법 1. exit 입력 후 다시 SSH 접속
exit
# 방법 2. 그냥 즉시 적용
newgrp docker
그 후 다시 실행해보자.
docker compose up -d
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
16ee08a8de77 n8nio/n8n "tini -- /docker-ent…" 9 seconds ago Up Less than a second 0.0.0.0:5678->5678/tcp, [::]:5678->5678/tcp n8n-docker-n8n-1
c93e2d4b230e postgres:13 "docker-entrypoint.s…" 9 seconds ago Up 8 seconds 5432/tcp
docker compose ps
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
n8n-docker-n8n-1 n8nio/n8n "tini -- /docker-ent…" n8n 40 seconds ago Up 1 second 0.0.0.0:5678->5678/tcp, [::]:5678->5678/tcp
n8n-docker-postgresql-1 postgres:13 "docker-entrypoint.s…" postgresql 40 seconds ago Up 39 seconds 5432/tcp
🥳 성공!
2. Docker 실행 후 브라우저에서 n8n 접속 실패

분명 정상적으로 도커가 실행되고 있는데.. 막상 n8n은 접속이 안되고 있었다..
생각해보니 지금 EC2에 설치한 상황인데 인바운드 규칙 설정을 까먹고 있었다..

역시나 80과 22만 열려있고 나머진 안열려있었다..
현재 포트 세팅은 5678로 되어있으니 똑같이 5678을 열여주면 해결된다.
⭐ 단, 나중에 Nginx를 붙이면 해당 규칙은 삭제하자.
3. Error: EACCES: permission denied, open '/home/node/.n8n/config’
여전히 n8n 접속이 안되고 있어서 로그를 다시 출력해봤다.
docker logs -f n8n-docker-n8n-1
# No encryption key found - Auto-generating and saving to: /home/node/.n8n/config
# No encryption key found - Auto-generating and saving to: /home/node/.n8n/config
# Error: EACCES: permission denied, open '/home/node/.n8n/config'
AI에게 물어본 결과 해당 오류는 다음과 같다고 한다.
→ n8n 컨테이너가 .n8n 디렉터리에 파일을 쓰려고 하는데 호스트에 마운트된 볼륨의 권한이 맞지 않아서 발생한 오류
현재 docker-compose.yml을 다시 확인해보자.
volumes:
- ./n8n_data:/home/node/.n8n
도커 컨테이너 내부는 유저:node (UID 1000) 으로 설정되어있는데
EC2 호스트는 ./n8n_data 디렉터리, 소유자는 ubuntu이기에 권한이 아예 존재하지 않는 상태이다.
즉, UID 불칠이롸 쓰기 권한이 없는 상태여서 발생한 오류이다.
따라서 잠시 컨테이너를 멈추고 호스트에서 권한을 수정하자.
docker compose down
# n8n은 UID 1000으로 실행되기 때문에 아래처럼 작성하자.
## chown -R 1000:1000 : ./n8n_data 디렉터리와 모든 하위 파일들의 소유자를 1000:1000으로 변경한다.
## * n8n의 Docker 컨테이너는 기본적으로 UID/GID 1000으로 실행되므로 호스트의 데이터 볼륨을 컨테이너 내부에서 읽고 쓸 수 있도록 권한을 맞춰야한다.
sudo chown -R 1000:1000 ./n8n_data
# rwxr-xr-x가 나오면 성공
ls -ld ./n8n_data
## drwxr-xr-x 2 ubuntu ubuntu 4096 Jan 17 13:55 ./n8n_data
# 실행 후 로그 확인
docker compose up -d
docker logs -f n8n-dockern8n-1
# 뭔가 마이그레이션이 막 뜨다가 아래애 아래처럼 뜨면 성공
Registered runner "JS Task Runner" (_71xI0cYhbmmcTbkam_ho)
Version: 2.3.6
Editor is now accessible via:
http://xxx.xxx.xxx.xxx:5678
4. Your n8n server is configured to use a secure cookie...
여전히 n8n 서비스에 접속이 안되고 있다…..

n8n은 기본적으로 Secure Cookie를 사용한다.
근데 지금 접속은 HTTP (또는 Safari)이기 때문에 로그인 쿠키를 브라우저에 심을 수 없어서 아예 접근을 막아버린 상황이다.
그냥 쉽게 말하면 로컬이 아닌 외부 접속을 시도할 경우 반드시 보안 접속이 있어야한다는 것이다.
본래는 반드시 https를 서버에 적용한 후 보안 접속을 시도 해야 하지만 현재는 학습 목적이기에 N8N_SECURE_COOKIE를 false로 비활성화해보자.
environment에 N8N_SECURE_COOKIE=false를 추가한 후 다시 접속하면 아래와 같이 뜨며 성공한다.

참고 자료
n8n 이란? - 개인 및 회사 내부에서 사용이 가능한 워크플로우 자동화 도구 - TILNOTE