Docker Compose로 n8n 프로덕션 환경 구축하기 (Traefik + PostgreSQL + Redis)

Traefik + PostgreSQL + Redis + Worker 구성의 프로덕션급 n8n 환경을 Docker Compose로 구축하는 전 과정을 정리했습니다.

Docker Compose로 n8n 프로덕션 환경 구축하기 (Traefik + PostgreSQL + Redis)

n8n은 Zapier나 Make(구 Integromat)와 유사한 오픈소스 워크플로우 자동화 도구입니다. 직접 서버에 호스팅하여 데이터를 외부로 보내지 않고 사용할 수 있고, 400개 이상의 서비스 연동과 커스텀 코드(JavaScript/Python) 실행을 지원합니다. 이 가이드에서는 단순한 단일 컨테이너 구성이 아닌, 도메인 + TLS + PostgreSQL + Redis + Worker를 갖춘 프로덕션급 환경을 Docker Compose로 구축하는 방법을 정리합니다.

아키텍처 개요

이 가이드에서 구축할 스택은 다음과 같습니다.

[브라우저]
    │ HTTPS
    ▼
[Traefik] ← Let's Encrypt TLS 자동 발급/갱신
    │
    ▼
[n8n Master] ── 워크플로우 실행 요청 ──▶ [Redis Queue]
                                               │
                                               ▼
                                        [n8n Worker]
                                               │
                               ┌───────────────┘
                               ▼
                         [PostgreSQL] ← 워크플로우/실행 이력 저장

각 컴포넌트의 역할은 다음과 같습니다.

  • Traefik: 리버스 프록시. Let's Encrypt를 통해 HTTPS 인증서를 자동으로 발급·갱신합니다.
  • PostgreSQL: 프로덕션 데이터베이스. n8n 기본 SQLite 대신 사용하여 안정성과 성능을 확보합니다.
  • Redis: 워크플로우 실행 Queue. Master 노드가 실행 요청을 Queue에 쌓으면 Worker가 처리합니다.
  • n8n Master: UI 제공 및 워크플로우 관리. 실행 요청을 Redis Queue에 전달합니다.
  • n8n Worker: Queue에서 작업을 가져와 실제 워크플로우를 실행합니다. 수평 확장이 가능합니다.

사전 요구사항

  • Docker 및 Docker Compose 설치
  • 외부에서 접근 가능한 서버 (80, 443 포트 오픈)
  • 도메인 및 DNS A 레코드 설정 (n8n.your-domain.com → 서버 IP)
# Docker 버전 확인
docker --version

# Docker Compose 버전 확인
docker compose version

# Timezone 설정 (서울)
sudo timedatectl set-timezone Asia/Seoul
timedatectl

1. 프로젝트 디렉토리 및 Docker Network 생성

mkdir ~/n8n && cd ~/n8n

docker network create n8n

프로젝트 최종 디렉토리 구조는 다음과 같습니다.

~/n8n/
├── docker-compose.yaml
├── .env
├── traefik/
│   └── traefik.yml
├── acme/
│   └── acme.json       # Let's Encrypt 인증서 저장
└── logs/               # Traefik 로그

2. Traefik 설정

Traefik은 Docker 라벨 기반으로 라우팅을 자동으로 감지합니다. Let's Encrypt HTTP Challenge 방식으로 TLS 인증서를 발급합니다.

mkdir ~/n8n/traefik ~/n8n/acme ~/n8n/logs
vi ~/n8n/traefik/traefik.yml
global:
  checkNewVersion: false
  sendAnonymousUsage: false

api:
  dashboard: true
  insecure: false

log:
  level: INFO
  format: json
  filePath: /logs/traefik.log

accessLog:
  filePath: /logs/access.log

entryPoints:
  web:
    address: ":80"
    http:
      redirections:
        entrypoint:
          to: websecure
          scheme: https

  websecure:
    address: ":443"

providers:
  docker:
    endpoint: "unix:///var/run/docker.sock"
    exposedByDefault: false
    network: n8n
    watch: true

certificatesResolvers:
  letsencrypt:
    acme:
      email: <your-email@example.com>        # Let's Encrypt 알림 수신용 이메일
      storage: /acme/acme.json
      httpChallenge:
        entryPoint: web

Let's Encrypt 인증서 저장 파일을 생성합니다. 권한은 반드시 600이어야 합니다.

touch ~/n8n/acme/acme.json
chmod 600 ~/n8n/acme/acme.json

3. .env 파일 작성

민감한 정보는 .env 파일에서 관리합니다.

vi ~/n8n/.env
# 도메인 설정
N8N_HOST=n8n.<your-domain.com>
N8N_PROTOCOL=https
TRAEFIK_HOST=traefik.<your-domain.com>

# n8n 암호화 키 (임의의 긴 문자열로 변경)
N8N_ENCRYPTION_KEY=change-this-to-a-random-secret-key

# 데이터베이스 비밀번호
DB_POSTGRESDB_PASSWORD=change-this-db-password

# Redis 비밀번호
REDIS_PASSWORD=change-this-redis-password

# 타임존
GENERIC_TIMEZONE=Asia/Seoul

# n8n 컨테이너 실행 사용자 (권한 문제 방지)
UID=1000
GID=1000

N8N_ENCRYPTION_KEY는 n8n이 Credential(API 키, 패스워드 등)을 암호화할 때 사용합니다. 분실하면 저장된 모든 Credential을 복구할 수 없으니 반드시 안전하게 보관하세요.

4. docker-compose.yaml 작성

모든 서비스를 하나의 파일로 정의합니다.

vi ~/n8n/docker-compose.yaml
services:

  # ── Reverse Proxy ──────────────────────────────────────
  traefik:
    image: traefik:v3.0
    container_name: traefik
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./traefik/traefik.yml:/etc/traefik/traefik.yml:ro
      - ./acme:/acme
      - ./logs:/logs
    networks:
      - n8n
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.dashboard.rule=Host(`${TRAEFIK_HOST}`)"
      - "traefik.http.routers.dashboard.service=api@internal"
      - "traefik.http.routers.dashboard.tls=true"
      - "traefik.http.routers.dashboard.tls.certresolver=letsencrypt"

  # ── Database ───────────────────────────────────────────
  postgres:
    image: postgres:16
    container_name: postgres
    restart: always
    environment:
      - POSTGRES_USER=n8n
      - POSTGRES_PASSWORD=${DB_POSTGRESDB_PASSWORD}
      - POSTGRES_DB=n8n
    volumes:
      - postgres-data:/var/lib/postgresql/data
    networks:
      - n8n
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U n8n"]
      interval: 10s
      timeout: 5s
      retries: 5

  # ── Queue (Redis) ──────────────────────────────────────
  redis:
    image: redis:7-alpine
    container_name: redis
    restart: always
    command: redis-server --requirepass ${REDIS_PASSWORD}
    volumes:
      - redis-data:/data
    networks:
      - n8n
    healthcheck:
      test: ["CMD", "redis-cli", "--no-auth-warning", "-a", "${REDIS_PASSWORD}", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5

  # ── n8n Master ─────────────────────────────────────────
  n8n:
    image: docker.n8n.io/n8nio/n8n
    container_name: n8n
    user: "${UID}:${GID}"
    restart: always
    volumes:
      - ~/.n8n:/home/node/.n8n
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    environment:
      # 외부 접근 URL
      - N8N_HOST=${N8N_HOST}
      - N8N_PORT=5678
      - N8N_PROTOCOL=${N8N_PROTOCOL}
      - N8N_WEBHOOK_URL=https://${N8N_HOST}
      - NODE_ENV=production
      - N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
      # 불필요한 텔레메트리 비활성화
      - N8N_DIAGNOSTICS_ENABLED=false
      - N8N_VERSION_NOTIFICATIONS_ENABLED=false
      - GENERIC_TIMEZONE=${GENERIC_TIMEZONE}
      # PostgreSQL
      - DB_TYPE=postgresdb
      - DB_POSTGRESDB_HOST=postgres
      - DB_POSTGRESDB_PORT=5432
      - DB_POSTGRESDB_DATABASE=n8n
      - DB_POSTGRESDB_USER=n8n
      - DB_POSTGRESDB_PASSWORD=${DB_POSTGRESDB_PASSWORD}
      # Redis Queue
      - EXECUTIONS_MODE=queue
      - QUEUE_BULL_REDIS_HOST=redis
      - QUEUE_BULL_REDIS_PORT=6379
      - QUEUE_BULL_REDIS_PASSWORD=${REDIS_PASSWORD}
    networks:
      - n8n
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.n8n.rule=Host(`${N8N_HOST}`)"
      - "traefik.http.routers.n8n.tls=true"
      - "traefik.http.routers.n8n.tls.certresolver=letsencrypt"
      - "traefik.http.services.n8n.loadbalancer.server.port=5678"

  # ── n8n Worker ─────────────────────────────────────────
  n8n-worker:
    image: docker.n8n.io/n8nio/n8n
    container_name: n8n-worker
    user: "${UID}:${GID}"
    restart: always
    command: worker
    volumes:
      - ~/.n8n:/home/node/.n8n
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    environment:
      - NODE_ENV=production
      - N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
      - GENERIC_TIMEZONE=${GENERIC_TIMEZONE}
      # PostgreSQL
      - DB_TYPE=postgresdb
      - DB_POSTGRESDB_HOST=postgres
      - DB_POSTGRESDB_PORT=5432
      - DB_POSTGRESDB_DATABASE=n8n
      - DB_POSTGRESDB_USER=n8n
      - DB_POSTGRESDB_PASSWORD=${DB_POSTGRESDB_PASSWORD}
      # Redis Queue
      - EXECUTIONS_MODE=queue
      - QUEUE_BULL_REDIS_HOST=redis
      - QUEUE_BULL_REDIS_PORT=6379
      - QUEUE_BULL_REDIS_PASSWORD=${REDIS_PASSWORD}
    networks:
      - n8n

networks:
  n8n:
    external: true

volumes:
  postgres-data:
  redis-data:

주요 설정 포인트:

  • N8N_PORT=5678: n8n이 컨테이너 내부에서 리스닝하는 포트입니다. 외부에서는 Traefik이 443으로 받아 5678로 포워딩합니다.
  • traefik.http.services.n8n.loadbalancer.server.port=5678: Traefik이 n8n 컨테이너의 5678 포트로 트래픽을 전달하도록 지정합니다.
  • user: "${UID}:${GID}": n8n 컨테이너가 root로 실행되면 ~/.n8n 디렉토리가 root 소유로 생성되어 권한 문제가 발생합니다. 현재 사용자 UID/GID를 지정하여 방지합니다.
  • depends_on + healthcheck: PostgreSQL과 Redis가 완전히 준비된 후에 n8n이 시작되도록 합니다. healthcheck 없이 depends_on만 쓰면 컨테이너가 뜨는 시점(준비 완료 전)에 n8n이 연결을 시도해 실패할 수 있습니다.
  • EXECUTIONS_MODE=queue: Worker 모드 활성화 설정입니다. 이 값이 있어야 Master가 Redis Queue에 작업을 넣고, Worker가 꺼내서 실행하는 구조로 동작합니다.

5. 실행

cd ~/n8n
docker compose up -d

컨테이너 상태를 확인합니다.

docker compose ps

정상 실행 시 아래와 같이 출력됩니다.

NAME          IMAGE                        STATUS
traefik       traefik:v3.0                 Up
postgres      postgres:16                  Up (healthy)
redis         redis:7-alpine               Up (healthy)
n8n           docker.n8n.io/n8nio/n8n      Up
n8n-worker    docker.n8n.io/n8nio/n8n      Up

n8n 로그를 확인합니다.

docker compose logs -f n8n

브라우저에서 https://n8n.<your-domain.com> 으로 접속하면 초기 계정 설정 화면이 나타납니다.

6. 백업

주기적인 백업을 위한 스크립트를 작성합니다.

vi ~/n8n/backup-n8n.sh
#!/bin/bash

BACKUP_PATH="/backup/n8n"
BACKUP_NAME="n8n-backup-$(date +%Y%m%d-%H%M%S)"
N8N_PATH=~/n8n

mkdir -p $BACKUP_PATH
cd $N8N_PATH

# PostgreSQL 덤프
docker compose exec -T postgres pg_dump -U n8n n8n > $BACKUP_PATH/$BACKUP_NAME.sql

# n8n 설정 및 워크플로우 백업
tar -czf $BACKUP_PATH/$BACKUP_NAME-data.tar.gz -C ~ .n8n

# 30일 이상 된 백업 정리
find $BACKUP_PATH -name "n8n-backup-*" -type f -mtime +30 -delete

echo "Backup completed: $BACKUP_PATH/$BACKUP_NAME"
chmod +x ~/n8n/backup-n8n.sh

매일 자정 자동 백업을 위한 cron 설정입니다.

(crontab -l ; echo "0 0 * * * ~/n8n/backup-n8n.sh >> ~/n8n/backup.log 2>&1") | crontab -

7. 업데이트

n8n 이미지를 최신 버전으로 업데이트할 때는 백업 후 이미지를 교체합니다.

cd ~/n8n

# 백업 먼저 실행
./backup-n8n.sh

# 최신 이미지 Pull
docker compose pull

# 재시작
docker compose down
docker compose up -d

# 로그 확인
docker compose logs -f n8n

마치며

이 가이드에서 구축한 n8n 스택의 핵심 포인트는 세 가지입니다.

  1. Traefik: 별도 Certbot 없이 Let's Encrypt 인증서를 자동으로 관리해 줍니다. 컨테이너 추가 시 라벨만 붙이면 자동으로 라우팅을 잡아줘서 확장이 편리합니다.
  2. Queue 모드(Redis + Worker): 워크플로우가 많아지면 단일 n8n 인스턴스가 병목이 됩니다. Worker를 분리하면 n8n-worker 서비스만 scale out하여 처리량을 높일 수 있습니다.
  3. PostgreSQL + healthcheck: SQLite는 동시 쓰기에 약하고 파일 기반이라 백업이 번거롭습니다. PostgreSQL을 쓰면 표준 DB 백업 방식을 그대로 적용할 수 있고, healthcheck로 기동 순서를 보장해 초기화 타이밍 문제를 예방합니다.

n8n은 사용자가 직접 커스텀 코드를 실행할 수 있어 Zapier 같은 SaaS 대비 훨씬 유연합니다. 한번 셋업해 두면 반복 업무를 자동화하는 데 강력한 도구가 됩니다.