n8n YouTube 자막(Transcript) 추출 – 성공 구성 정리
환경: ① Dockge + Docker Compose, ② Home Assistant OS(HAOS) 로컬 애드온(Apps) — 둘 다 linux/arm64 (musl/Alpine) 기반 사용 노드:
n8n-nodes-youtube-dl(yt-dlp 기반) 최종 확인일: 2026-08-04
문제 요약
n8n 공식 이미지(n8nio/n8n:next)가 v2.x부터 distroless(패키지 매니저 없음) 로 바뀌면서, YouTube 자막 추출에 필요한 의존성(Python, yt-dlp, JS 런타임)을 설치하기 위해 커스텀 Dockerfile이 필요했다. 추가로 아래 4개 문제가 순차적으로 겹쳐 있었다.
- apk 부재 — n8n v2.x 이미지에 패키지 매니저 자체가 없음
- musl/glibc 비호환 — yt-dlp의 PyInstaller 바이너리가 Alpine(musl)에서 실행 불가
- YouTube bot 차단 — 서버 IP에서의 요청을 봇으로 의심 (“Sign in to confirm you’re not a bot”)
- YouTube SABR/EJS 챌린지 — 2026년 YouTube가 도입한 JS 챌린지 인증, JS 런타임(deno) + 챌린지 solver 스크립트 필요
최종 Dockerfile
FROM alpine:latest AS alpine
FROM docker.n8n.io/n8nio/n8n:next
USER root
# n8n v2.x 이미지에는 apk가 없으므로 Alpine 이미지에서 바이너리를 복사해온다
COPY --from=alpine /sbin/apk /sbin/apk
COPY --from=alpine /usr/lib/libapk.so* /usr/lib/
COPY --from=alpine /etc/apk /etc/apk
COPY --from=alpine /lib/apk /lib/apk
RUN apk update && \
# yt-dlp는 PyInstaller 바이너리 대신 pip 순수 Python 버전을 사용 (musl 호환 문제 회피)
apk add --no-cache python3 py3-pip && \
pip install --break-system-packages yt-dlp && \
# deno는 apk edge 저장소의 musl 네이티브 빌드를 사용 (curl 설치 스크립트는 glibc 바이너리라 실패함)
apk add --no-cache --repository https://dl-cdn.alpinelinux.org/alpine/edge/community deno
USER node
왜 이렇게 구성했는가
| 구성 요소 | 방식 | 이유 |
|---|---|---|
| apk | Alpine 이미지에서 바이너리 복사 | n8n v2.x 이미지는 패키지 매니저가 완전히 제거된 distroless 구조 |
| yt-dlp | pip install (PyInstaller 바이너리 X) | standalone 바이너리는 glibc 기반이라 musl(Alpine)에서 dladdr1: symbol not found 등으로 실행 실패. gcompat으로도 완전히 해결 안 됨 |
| deno | apk add --repository .../edge/community deno | curl -fsSL https://deno.land/install.sh 스크립트로 받는 바이너리는 glibc용이라 Alpine에서 실행 불가 (__res_init: symbol not found). Alpine edge 저장소의 musl 네이티브 빌드를 써야 함 |
최종 compose.yaml
services:
n8n:
build:
context: .
dockerfile: Dockerfile
restart: always
ports:
- 5678:5678
environment:
- N8N_HOST=n8n-w.duckdns.org
- N8N_PORT=5678
- N8N_PROTOCOL=https
- N8N_SECURE_COOKIE=false
- N8N_DEFAULT_LOCALE=ko
- N8N_BASIC_AUTH_PROXY=true
- N8N_ENABLED_MODULES=chat-hub
- NODE_ENV=production
- WEBHOOK_URL=https://n8n-w.duckdns.org/
- GENERIC_TIMEZONE=Asia/Seoul
- N8N_ENCRYPTION_KEY=tthGpOU6HiiJAaCc13D5VNmi31yC1BLM
- YT_DLP_PATH=/usr/bin/yt-dlp
volumes:
- /opt/n8n/node:/home/node
- /opt/n8n/files:/files
- /opt/n8n/cookies:/home/node/cookies # CLI 디버깅용 (노드 자체는 아래 Credential을 사용)
networks: {}
/opt/n8n/cookies볼륨은 노드 동작에는 필수가 아니며, CLI(docker exec)로 직접 yt-dlp를 테스트/디버깅할 때 사용한다. 실제 워크플로우는 아래 YouTube Cookies Credential을 사용한다.
HAOS 로컬 애드온(Apps) 버전 구성
Dockge/Compose와 별개로, Home Assistant OS의 로컬 애드온으로도 동일한 n8n(+ YouTube 자막 기능)을 운영 중이라면 아래처럼 구성한다. HAOS 애드온은 config.yaml이 compose.yaml 역할을, Supervisor가 docker-compose/Dockge 역할을 대신한다.
config.yaml
name: "n8n"
version: "2.34.0"
slug: "n8n"
description: "Workflow automation tool (n8n) running as a Home Assistant App (with YouTube transcript support)"
url: "https://github.com/n8n-io/n8n"
arch:
- amd64
- aarch64
# image 키를 쓰지 않는다 -> Supervisor가 같은 폴더의 Dockerfile로 직접 빌드하도록 강제
# (image 키가 있으면 Supervisor는 사전 빌드 이미지를 그냥 pull만 하고 Dockerfile은 무시함)
startup: services
boot: auto
webui: "http://[HOST]:[PORT:5678]/"
ports:
5678/tcp: 5678
ports_description:
5678/tcp: "n8n web UI / webhook 포트"
map:
- type: share
read_only: false
path: /files
- type: addon_config
read_only: false
path: /home/node
# 쿠키 파일이 필요하면 이 볼륨 안에 cookies/cookies.txt 로 두면 됨
# (호스트 경로: addon_configs/<slug>/cookies/cookies.txt)
environment:
N8N_HOST: "w-n8n.duckdns.org"
N8N_PORT: "5678"
N8N_PROTOCOL: "https"
N8N_PROXY_HOPS: "1"
N8N_SECURE_COOKIE: "false"
N8N_DEFAULT_LOCALE: "ko"
N8N_BASIC_AUTH_PROXY: "true"
N8N_ENABLED_MODULES: "chat-hub"
NODE_ENV: "production"
WEBHOOK_URL: "https://w-n8n.duckdns.org/"
GENERIC_TIMEZONE: "Asia/Seoul"
N8N_ENCRYPTION_KEY: "tthGpOU6HiiJAaCc13D5VNmi31yC1BLM"
YT_DLP_PATH: "/usr/bin/yt-dlp"
options: {}
schema: {}
Dockerfile (config.yaml과 같은 폴더에 위치)
ARG BUILD_VERSION=latest
FROM alpine:latest AS alpine
# 주의: docker.n8n.io가 아닌 Docker Hub(docker.io)를 사용한다 (아래 트러블슈팅 참고)
FROM docker.io/n8nio/n8n:${BUILD_VERSION}
USER root
COPY --from=alpine /sbin/apk /sbin/apk
COPY --from=alpine /usr/lib/libapk.so* /usr/lib/
COPY --from=alpine /etc/apk /etc/apk
COPY --from=alpine /lib/apk /lib/apk
RUN apk update && \
apk add --no-cache python3 py3-pip && \
pip install --break-system-packages --no-cache-dir yt-dlp && \
apk add --no-cache --repository https://dl-cdn.alpinelinux.org/alpine/edge/community deno && \
rm -rf /var/cache/apk/* /tmp/* /root/.cache
USER node
핵심 포인트
| 항목 | 설명 |
|---|---|
image: 키 제거 | 있으면 Supervisor가 Dockerfile을 무시하고 사전 빌드 이미지를 그대로 pull함. 커스텀 빌드를 쓰려면 반드시 제거 |
ARG BUILD_VERSION | Supervisor가 빌드 시 config.yaml의 version 값을 --build-arg BUILD_VERSION=...으로 자동 주입. FROM ...${BUILD_VERSION}으로 받으면 config.yaml의 version만 올려도 자동 반영됨 |
ARG BUILD_VERSION=latest (기본값) | 기본값을 지정하지 않으면 InvalidDefaultArgInFrom 경고가 뜸 (치명적이진 않음). Supervisor가 항상 실제 값을 주입하므로 latest는 실사용되지 않는 안전한 placeholder |
FROM docker.io/... (Docker Hub) | docker.n8n.io(n8n 자체 레지스트리)가 아닌 Docker Hub를 사용. 아래 트러블슈팅 참고 |
| 설치+정리를 한 RUN 레이어에 | rm -rf ...를 별도 RUN으로 나누면 레이어가 분리되어 이미지 용량이 줄지 않음. 설치와 캐시 정리를 같은 레이어에서 함께 처리해야 실제로 용량 절감 효과가 있음 |
⚠️ 트러블슈팅: docker.n8n.io 429 Too Many Requests
증상
ERROR: unexpected status from HEAD request to https://docker.n8n.io/v2/n8nio/n8n/manifests/2.34.0: 429 Too Many Requests
원인: docker.n8n.io는 n8n이 자체 운영하는 이미지 배포처로 Docker Hub와는 별개 인프라다. 여러 버전(2.33.3, 2.34.0 등)으로 바꿔서 시도해도 동일하게 실패했다면, 특정 버전/최초 시도 여부와 무관하게 이 레지스트리(또는 앞단 CDN)가 해당 요청을 rate limit으로 막고 있는 상태로 판단할 수 있다. Supervisor가 빌드 시 --pull 옵션으로 매번 메타데이터를 새로 조회하는데, 이 조회 자체가 막혀 있었다.
해결: Dockerfile의 FROM 대상을 docker.n8n.io/n8nio/n8n:${BUILD_VERSION} → docker.io/n8nio/n8n:${BUILD_VERSION}(Docker Hub)로 바꾸는 것만으로 즉시 해결됨. 같은 이미지의 다른 배포처를 쓰는 것이므로 다른 부작용은 없다.
참고: Docker Hub도 비로그인 pull은 IP당 시간당 100회 제한이 있으므로, 너무 잦은 rebuild는 이쪽에서도 429가 날 수 있다.
애드온 버전 업그레이드 절차
- config.yaml의
version값만 새 버전으로 수정 (예:"2.34.0"→"2.35.0") - Supervisor → 애드온 → Rebuild
- Dockerfile은 그대로 두면 됨 (
${BUILD_VERSION}이 자동으로 새 버전을 참조)
n8n 노드 설정 (YouTube Downloader / Get Transcript)
Credential: YouTube Cookies
- 브라우저 확장 프로그램 EditThisCookie (V3) 설치
- YouTube에 로그인한 상태에서 youtube.com 쿠키를 JSON 형식으로 Export (클립보드 복사)
- n8n → Credentials → Add Credential → YouTube Cookies
- 복사한 JSON을 붙여넣고 저장
Custom yt-dlp Flags
노드 파라미터의 Custom yt-dlp Flags 필드에 아래 값을 입력:
--remote-components ejs:github
- YouTube의 SABR 스트리밍 강제 전환에 대응하기 위해, yt-dlp가 JS 챌린지 solver 스크립트(EJS)를 GitHub에서 자동으로 받아오도록 지정하는 옵션
- deno 런타임은 Dockerfile에서 이미 설치되어 있으므로, 이 플래그만 추가하면 챌린지가 자동으로 풀림
문제 해결 시 확인 순서 (체크리스트)
향후 n8n/yt-dlp 업데이트 후 자막 추출이 다시 안 될 경우, 아래 순서로 원인을 좁혀간다.
yt-dlp 실행 자체가 되는가?
docker exec -it n8n-n8n-1 yt-dlp --version버전이 안 뜨면 → Dockerfile의 pip 설치 단계 확인
deno가 정상 실행되는가?
docker exec -it n8n-n8n-1 deno --versionPermission denied,symbol not found등이 뜨면 → musl 네이티브 빌드가 아닌 바이너리가 설치된 것. apk edge 저장소 방식으로 재설치CLI에서 쿠키 포함 자막 추출이 되는가? (노드를 거치지 않고 직접 확인)
docker exec -it n8n-n8n-1 yt-dlp \
--skip-download --write-auto-sub --sub-lang en \
--cookies /home/node/cookies/cookies.txt \
--remote-components ejs:github \
-o /tmp/test "https://www.youtube.com/watch?v=VIDEO_ID"Sign in to confirm you're not a bot→ 쿠키 만료. 재로그인 후 쿠키 재발급 필요Requested format is not available→ EJS/deno 관련 문제.--remote-components ejs:github플래그 확인- 정상적으로
.vtt파일이 다운로드되면 → yt-dlp/서버 쪽은 정상. 노드 쪽 파라미터(Custom Flags, Credential 연결 여부) 확인
위 3번이 성공하는데 n8n 노드 실행만 실패한다면
- 노드의 Custom yt-dlp Flags에
--remote-components ejs:github가 들어있는지 확인 - YouTube Cookies Credential이 워크플로우 노드에 실제로 선택(연결)되어 있는지 확인
- Credential의 쿠키가 최신인지 확인 (재로그인 후 재발급)
- 노드의 Custom yt-dlp Flags에
쿠키 만료 시 재설정 절차 (요약)
증상: Sign in to confirm you're not a bot 에러 재발생
- 브라우저에서 YouTube 재로그인
- EditThisCookie (V3) → Export (JSON, 클립보드 복사)
- n8n → Credentials → YouTube Cookies → 기존 값 삭제 후 붙여넣기 → Save
- 워크플로우 재실행
참고: 구글 계정 보안상 세션 로그아웃을 하면 기존에 내보낸 모든 쿠키(파일 포함)가 즉시 무효화된다. 쿠키 파일이나 JSON은 채팅/외부 서비스에 절대 업로드하지 말고, 로컬 → 서버로 직접 전송(scp) 또는 n8n 화면에 직접 붙여넣기만 할 것.