홈랩 K3s 클러스터에서 n8n을 셀프호스팅으로 쓰고 있다. 오늘 기준 버전 2.31.6, 워크플로 13개다. 이 글은 기능 소개가 아니다. 13개를 굴리면서 “이렇게 쓰면 편하고, 이렇게 쓰면 나중에 아프다”고 정리한 규칙이다.

결론부터 적으면 이렇다.

  • n8n은 “정해진 시각에 정해진 일을 하고 사람에게 알리는” 곳에 쓴다. 판단이 많은 일은 코드나 에이전트로 보낸다.
  • 워크플로 이름에 스케줄을 적는다. 13개가 넘어가면 이름이 곧 운영 대시보드다.
  • 셀프호스팅이면 암호화 키와 DB부터 챙긴다. 기본값 그대로 두면 볼륨 하나를 잃을 때 자격 증명이 전부 복호화 불가가 된다.

1. 지금 돌고 있는 13개

n8n list:workflow로 뽑은 목록을 성격별로 묶었다.

묶음 워크플로 하는 일
학습 코테 오늘의 문제 (07:00) / 풀이 (21:00), SQL 오늘의 문제 (07:30) / 정답 (21:30) 아침에 문제, 저녁에 답을 텔레그램으로 보낸다
마음 아침명언봇 (08:30) 명언 한 줄
데이터 etl-dart-ecos-daily DART·한국은행 ECOS 데이터를 매일 적재
운영 ① ETL watermark 감시 (09:00), ② 도메인 헬스체크 (30분), ③ solomon 무선 강등 감시 (5분), ④ velero 백업 요약 (08:00), ⑥ 주간 회고 (일 21:00) 파이프라인·도메인·무선 링크·백업 상태를 보고 이상하면 알린다
AI 비용 Claude Code 사용량 요약 (08:57), Kiro 사용량 요약 (08:58) 전날 AI 도구 사용량을 요약해 보낸다

전부 스케줄 트리거로 시작해서 텔레그램 메시지로 끝난다. 이게 우연이 아니다. 아래 규칙 1이 그 이유다.

2. 규칙 1 — n8n에 맡길 일, 맡기지 않을 일

n8n이 가장 잘하는 일은 이 모양이다.

[시계 또는 웹훅] → [API 한두 개 호출] → [가볍게 가공] → [사람에게 전달]
  • 맡긴다: 정기 발송, 상태 수집과 요약, API끼리 붙이기, 사람에게 알리기. 노드를 보면 흐름이 한눈에 들어오고, 실패한 실행을 UI에서 다시 돌릴 수 있다.
  • 맡기지 않는다: 분기가 많은 판단, 상태를 오래 들고 있어야 하는 일, 테스트가 필요한 로직. 예를 들어 정산 정합성 규칙이나 보안 경보 조사는 n8n이 아니라 테스트가 붙은 코드(정산 서비스)와 읽기 전용 에이전트(WATCHMAN)로 뺐다.

기준은 하나다. “이 로직이 틀렸을 때 테스트로 잡을 수 있어야 하는가?” 그렇다면 코드로 간다. n8n의 Code 노드 안에 수백 줄이 쌓이기 시작하면 그게 옮길 때라는 신호다.

3. 규칙 2 — 이름이 곧 운영 문서다

위 목록의 이름을 다시 보자. 운영③ solomon 무선 강등 감시 (5분)처럼 분류·번호·대상·주기가 이름에 다 들어 있다.

  • 주기를 이름에 적는다. 목록만 봐도 “지금 몇 시에 뭐가 도는지”가 보인다. 트리거 노드를 열어 볼 필요가 없다.
  • 번호를 붙인다. 운영①~⑥처럼 번호가 있으면 알림 메시지에 번호만 찍어도 어느 워크플로인지 안다. ⑤가 비어 있는 것도 그대로 기록이다. 지웠다는 뜻이다.
  • 시각을 겹치지 않게 흩는다. 08:57, 08:58처럼 1분씩 떨어뜨렸다. 같은 외부 API를 부르는 워크플로가 같은 순간에 몰리지 않게 하려는 것이다.

4. 규칙 3 — 알림은 “이상할 때만”, 요약은 “하루 한 번”

감시 워크플로가 매번 “정상입니다”를 보내면 사흘 안에 아무도 안 읽는다.

  • 감시형(②·③): 이상할 때만 보낸다. 5분마다 도는 무선 감시가 매번 메시지를 보냈다면 하루 288통이다.
  • 요약형(④·사용량 요약): 하루 한 번, 정해진 시각에 보낸다. 정상이어도 보낸다. 안 오면 그게 경보다. 요약 메시지가 오지 않은 날은 n8n 자체가 죽은 날이다.
  • 회고형(⑥): 일주일치를 모아 일요일 밤에 보낸다.

이 세 가지를 섞지 않는 게 요령이다. 감시와 요약을 한 워크플로에 넣으면 “이상 없음”이 알림으로 새어 나온다.

5. 규칙 4 — 실패를 워크플로로 받는다

n8n에는 Error Trigger 노드가 있다. 다른 워크플로가 실패하면 이 노드로 시작하는 “에러 워크플로”가 돈다. 각 워크플로 설정에서 에러 워크플로를 지정하면 된다(n8n 문서).

스케줄 워크플로가 조용히 실패하면 아무도 모른다. 정해진 시각에 오던 코테 문제가 안 오는 걸 알아채는 건 며칠 뒤다. 에러 워크플로 하나를 만들어서 실패 → 워크플로 이름과 실패 노드를 텔레그램으로 보내게 해 두면, 13개 전부가 한 번에 보호된다.

6. 규칙 5 — LLM 노드는 “요약”에만

사용량 요약 같은 워크플로는 LLM을 부른다. 원칙은 이렇다.

  • LLM은 마지막 단계에서 문장만 만든다. 숫자 계산과 집계는 앞 노드에서 끝낸다. LLM에게 “어제 합계를 내 줘”라고 하면 틀린 합계를 자신 있게 말한다.
  • LLM 호출은 게이트웨이를 거치게 한다. 모든 호출이 한곳을 지나면 비용을 원장처럼 맞출 수 있다. 우회 경로가 생기면 청구서가 먼저 알려 준다(정산 대사와 LLM 비용 대사).
  • 1분 스케줄에 LLM을 붙이지 않는다. 이번 달 Gemini 청구서가 전월 대비 +2,217%로 나왔다. 원인은 n8n이 아니라 다른 크론이었지만 구조는 같았다. 짧은 주기 × 숨은 LLM 호출이다(청구서 추적기).

7. 셀프호스팅이면 먼저 챙길 것

여기부터는 내 설정을 점검하다가 찾은 것들이다. 공식 문서 기준으로 정리했다.

① 암호화 키를 환경변수로 고정한다. n8n은 저장된 자격 증명(API 키, 봇 토큰 등)을 암호화한다. N8N_ENCRYPTION_KEY를 주지 않으면 첫 기동 때 무작위 키를 만들어 데이터 디렉터리에 저장한다(n8n 문서). 내 배포가 그 상태였다. 환경변수에 키가 없고, 키는 볼륨 안의 config 파일에만 있다. DB는 백업했는데 그 파일을 잃으면? 자격 증명 전부가 복호화 불가가 된다. 키를 비밀 저장소(sops 등)에 따로 두고 환경변수로 넣어야 한다.

② 실행 기록 정리 설정을 확인한다. 실행 기록은 기본으로 정리된다. EXECUTIONS_DATA_PRUNE 기본값은 true, 보존 기간 EXECUTIONS_DATA_MAX_AGE는 336시간(14일), 최대 건수는 10,000건이다(n8n 문서). 5분짜리 감시 하나만으로 하루 288건이니, 14일이면 약 4,000건이다. 워크플로가 늘면 건수 상한에 먼저 걸린다. 기본값을 알고 쓰자.

③ DB는 규모에 맞게. DB 종류를 지정하지 않으면 SQLite를 쓴다(n8n 문서). 내 DB는 지금 약 14MB다. 워크플로 13개를 혼자 쓰기엔 충분하다. 여러 인스턴스로 나눠 돌리는 큐 모드는 Redis와 Postgres가 필요하다(n8n 문서). 필요해지기 전에는 복잡도만 늘린다.

④ 이미지를 latest로 두지 않는다. 내 배포는 n8nio/n8n:latest에 digest를 고정해 두었다. 재시작할 때 몰래 올라가지는 않는다. 그런데 로그에 “이 릴리스는 6주보다 오래됐다”는 경고가 떴다. 고정은 좋지만 고정하고 잊으면 보안 패치도 같이 멈춘다. 명시적인 버전 태그와 정기 업그레이드 날짜를 정해 두는 편이 낫다.

8. 체크리스트

  • 이 일은 “시계/웹훅 → API → 가공 → 전달” 모양인가? 판단이 많으면 코드로 뺀다
  • 이름에 분류·번호·대상·주기가 있는가
  • 감시는 이상할 때만, 요약은 하루 한 번 보내는가
  • 에러 워크플로가 지정돼 있는가
  • LLM은 문장만 만들고, 계산은 앞 노드에서 끝내는가
  • N8N_ENCRYPTION_KEY를 볼륨 밖에 따로 보관했는가
  • 실행 기록 보존 기본값(14일, 10,000건)을 알고 있는가
  • 이미지 버전과 업그레이드 날짜가 정해져 있는가

한계

  • 워크플로 목록과 설정은 2026-09-28에 내 인스턴스에서 확인한 것이다. 워크플로 내부 노드 구성까지 전부 감사하지는 않았다.
  • 7절의 ①(암호화 키)과 ④(버전)는 내 배포에서 발견한 문제다. 이 글을 쓰는 시점에는 아직 고치지 않았다.

References