Давайте поговорим о нейросетях и автоматизациях

Летом 2025-го я впервые услышал про «Контент-завод».
Идея простая: автоматизация, которая «из ничего» штампует контент - текст, видео, аудио и т. д.
Меня это заинтересовало.
Знакомые из бизнеса говорили, что тема сейчас на хайпе.
Мимо пройти было нельзя :)

Инструмент для экспериментов - n8n. Наткнулся на него случайно (кажется, в Reels).
Эта статья - мой опыт: что делал по шагам и к чему пришёл.

Железо и стек

  • Основной инструмент: n8n

  • Сервер: «Std C1-M2-D10 - 10 ГБ SSD, 2 ГБ RAM, 1 vCPU»

  • Архитектура: Docker

  • Стек:

    • Postgres

    • WireGuard

    • Traefik

Где покупал сервер и откуда VPN - не скажу, чтобы не выглядело как реклама.

С чего начал

С настройки сервера. День ушёл на то, чтобы всё завелось и весь Docker ходил наружу через VPN, но при этом был доступен по моему домену (для вебхуков и интеграций это критично). В итоге получилась такая конструкция:

Doker файл
networks:
  web:
    name: web

x-logging: &rot
  driver: "local"
  options:
    max-size: "20m"
    max-file: "3"
    compress: "true"

services:
  traefik:
    image: traefik:v2.11
    container_name: traefik
    restart: unless-stopped
    command:
      - --providers.docker=true
      - --providers.docker.exposedbydefault=false
      - --providers.docker.network=web
      - --entrypoints.web.address=:80
      - --entrypoints.websecure.address=:443

      # Let's Encrypt (HTTP-01)
      - --certificatesresolvers.le.acme.email=${ACME_EMAIL}
      - --certificatesresolvers.le.acme.storage=/letsencrypt/acme.json
      - --certificatesresolvers.le.acme.httpchallenge=true
      - --certificatesresolvers.le.acme.httpchallenge.entrypoint=web

      # file-provider (если статические TLS/правила)
      - --providers.file.directory=/etc/traefik/dynamic
      - --providers.file.watch=true
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./traefik:/letsencrypt
      - ./traefik/dynamic:/etc/traefik/dynamic:ro
    networks: [web]
    logging: *rot

  postgres:
    image: postgres:16-alpine
    container_name: n8n-postgres
    restart: unless-stopped
    environment:
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - ./postgres-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB} -h 127.0.0.1"]
      interval: 10s
      timeout: 5s
      retries: 10
    networks: [web]
    logging: *rot

  # --- WireGuard VPN (клиент) ---
  wireguard:
    image: ghcr.io/linuxserver/wireguard:latest
    container_name: wireguard
    restart: unless-stopped
    cap_add: [NET_ADMIN, SYS_MODULE]
    sysctls:
      - net.ipv4.conf.all.src_valid_mark=1
      - net.ipv6.conf.all.disable_ipv6=0
    environment:
      - TZ=Europe/Amsterdam
      - PEERDNS=false
    volumes:
      - ./wireguard:/config
      - /lib/modules:/lib/modules:ro
    devices:
      - /dev/net/tun:/dev/net/tun
    networks: [web]
    labels:
      - "traefik.enable=true"
      # HTTP -> HTTPS
      - "traefik.http.routers.n8n-http.rule=Host(`${DOMAIN}`)"
      - "traefik.http.routers.n8n-http.entrypoints=web"
      - "traefik.http.routers.n8n-http.middlewares=n8n-redirect@docker"
      - "traefik.http.middlewares.n8n-redirect.redirectscheme.scheme=https"
      # HTTPS роутер
      - "traefik.http.routers.n8n.rule=Host(`${DOMAIN}`)"
      - "traefik.http.routers.n8n.entrypoints=websecure"
      - "traefik.http.routers.n8n.tls=true"
      # сервис на порт 5678 (n8n живёт в netns wireguard)
      - "traefik.http.services.n8n.loadbalancer.server.port=5678"
    logging: *rot

  n8n:
    image: n8nio/n8n:latest
    container_name: n8n
    restart: unless-stopped
    user: "1000:1000"
    network_mode: "service:wireguard"
    environment:
      # URL-ы и таймзона
      N8N_HOST: ${N8N_HOST}
      N8N_PORT: ${N8N_PORT}
      N8N_PROTOCOL: ${N8N_PROTOCOL}
      WEBHOOK_URL: ${WEBHOOK_URL}
      N8N_EDITOR_BASE_URL: ${N8N_EDITOR_BASE_URL}
      GENERIC_TIMEZONE: ${GENERIC_TIMEZONE}
      N8N_SECURE_COOKIE: ${N8N_SECURE_COOKIE}

      # ★ Продувка истории и меньше мусора
      EXECUTIONS_DATA_PRUNE: 'true'
      EXECUTIONS_DATA_MAX_AGE: '24'
      EXECUTIONS_DATA_PRUNE_MAX_COUNT: '0'
      EXECUTIONS_DATA_SAVE_ON_SUCCESS: 'none'
      EXECUTIONS_DATA_SAVE_ON_ERROR: 'all'
      EXECUTIONS_DATA_SAVE_ON_PROGRESS: 'false'

      # ★ Ограничители бесконечных циклов
      N8N_CONCURRENCY_PRODUCTION_LIMIT: '10'
      EXECUTIONS_TIMEOUT: '1800'
      N8N_LOG_LEVEL: 'warn'

      # ★ Бинарные данные - на файловую систему
      N8N_AVAILABLE_BINARY_DATA_MODES: 'filesystem'
      N8N_DEFAULT_BINARY_DATA_MODE: 'filesystem'
      N8N_BINARY_DATA_STORAGE_PATH: '/home/node/.n8n/binary_data'

      # БД
      DB_TYPE: postgresdb
      DB_POSTGRESDB_HOST: postgres
      DB_POSTGRESDB_PORT: 5432
      DB_POSTGRESDB_DATABASE: ${POSTGRES_DB}
      DB_POSTGRESDB_USER: ${POSTGRES_USER}
      DB_POSTGRESDB_PASSWORD: ${POSTGRES_PASSWORD}

      # Безопасность
      N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY}
      N8N_BASIC_AUTH_ACTIVE: ${N8N_BASIC_AUTH_ACTIVE}
      N8N_BASIC_AUTH_USER: ${N8N_BASIC_AUTH_USER}
      N8N_BASIC_AUTH_PASSWORD: ${N8N_BASIC_AUTH_PASSWORD}

      # (опционально) раннеры/права/ворнинги
      N8N_RUNNERS_ENABLED: 'true'
      N8N_BLOCK_ENV_ACCESS_IN_NODE: 'false'
      N8N_GIT_NODE_DISABLE_BARE_REPOS: 'true'

      # (опционально) телеметрия
      N8N_DIAGNOSTICS_ENABLED: "false"
      N8N_HIRING_BANNER_ENABLED: "false"

      # (рекомендуется) права на конфиг
      N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS: "true"
    volumes:
      - ./n8n-data:/home/node/.n8n
    depends_on:
      postgres:
        condition: service_healthy
      wireguard:
        condition: service_started
    logging: *rot

  n8n-mcp:
    image: ghcr.io/czlonkowski/n8n-mcp:latest
    container_name: n8n-mcp
    restart: unless-stopped
    environment:
      MCP_MODE: http
      HOST: 0.0.0.0
      LOG_LEVEL: ${MCP_LOG_LEVEL}
      AUTH_TOKEN: ${MCP_AUTH_TOKEN}
    networks:
  web:
    name: web

x-logging: &rot
  driver: "local"
  options:
    max-size: "20m"
    max-file: "3"
    compress: "true"

services:
  traefik:
    image: traefik:v2.11
    container_name: traefik
    restart: unless-stopped
    command:
      - --providers.docker=true
      - --providers.docker.exposedbydefault=false
      - --providers.docker.network=web
      - --entrypoints.web.address=:80
      - --entrypoints.websecure.address=:443

      # Let's Encrypt (HTTP-01)
      - --certificatesresolvers.le.acme.email=${ACME_EMAIL}
      - --certificatesresolvers.le.acme.storage=/letsencrypt/acme.json
      - --certificatesresolvers.le.acme.httpchallenge=true
      - --certificatesresolvers.le.acme.httpchallenge.entrypoint=web

      # file-provider (если статические TLS/правила)
      - --providers.file.directory=/etc/traefik/dynamic
      - --providers.file.watch=true
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./traefik:/letsencrypt
      - ./traefik/dynamic:/etc/traefik/dynamic:ro
    networks: [web]
    logging: *rot

  postgres:
    image: postgres:16-alpine
    container_name: n8n-postgres
    restart: unless-stopped
    environment:
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - ./postgres-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB} -h 127.0.0.1"]
      interval: 10s
      timeout: 5s
      retries: 10
    networks: [web]
    logging: *rot

  # --- WireGuard VPN (клиент) ---
  wireguard:
    image: ghcr.io/linuxserver/wireguard:latest
    container_name: wireguard
    restart: unless-stopped
    cap_add: [NET_ADMIN, SYS_MODULE]
    sysctls:
      - net.ipv4.conf.all.src_valid_mark=1
      - net.ipv6.conf.all.disable_ipv6=0
    environment:
      - TZ=Europe/Amsterdam
      - PEERDNS=false
    volumes:
      - ./wireguard:/config
      - /lib/modules:/lib/modules:ro
    devices:
      - /dev/net/tun:/dev/net/tun
    networks: [web]
    labels:
      - "traefik.enable=true"
      # HTTP -> HTTPS
      - "traefik.http.routers.n8n-http.rule=Host(`${DOMAIN}`)"
      - "traefik.http.routers.n8n-http.entrypoints=web"
      - "traefik.http.routers.n8n-http.middlewares=n8n-redirect@docker"
      - "traefik.http.middlewares.n8n-redirect.redirectscheme.scheme=https"
      # HTTPS роутер
      - "traefik.http.routers.n8n.rule=Host(`${DOMAIN}`)"
      - "traefik.http.routers.n8n.entrypoints=websecure"
      - "traefik.http.routers.n8n.tls=true"
      # сервис на порт 5678 (n8n живёт в netns wireguard)
      - "traefik.http.services.n8n.loadbalancer.server.port=5678"
    logging: *rot

  n8n:
    image: n8nio/n8n:latest
    container_name: n8n
    restart: unless-stopped
    user: "1000:1000"
    network_mode: "service:wireguard"
    environment:
      # URL-ы и таймзона
      N8N_HOST: ${N8N_HOST}
      N8N_PORT: ${N8N_PORT}
      N8N_PROTOCOL: ${N8N_PROTOCOL}
      WEBHOOK_URL: ${WEBHOOK_URL}
      N8N_EDITOR_BASE_URL: ${N8N_EDITOR_BASE_URL}
      GENERIC_TIMEZONE: ${GENERIC_TIMEZONE}
      N8N_SECURE_COOKIE: ${N8N_SECURE_COOKIE}

      # ★ Продувка истории и меньше мусора
      EXECUTIONS_DATA_PRUNE: 'true'
      EXECUTIONS_DATA_MAX_AGE: '24'
      EXECUTIONS_DATA_PRUNE_MAX_COUNT: '0'
      EXECUTIONS_DATA_SAVE_ON_SUCCESS: 'none'
      EXECUTIONS_DATA_SAVE_ON_ERROR: 'all'
      EXECUTIONS_DATA_SAVE_ON_PROGRESS: 'false'

      # ★ Ограничители бесконечных циклов
      N8N_CONCURRENCY_PRODUCTION_LIMIT: '10'
      EXECUTIONS_TIMEOUT: '1800'
      N8N_LOG_LEVEL: 'warn'

      # ★ Бинарные данные - на файловую систему
      N8N_AVAILABLE_BINARY_DATA_MODES: 'filesystem'
      N8N_DEFAULT_BINARY_DATA_MODE: 'filesystem'
      N8N_BINARY_DATA_STORAGE_PATH: '/home/node/.n8n/binary_data'

      # БД
      DB_TYPE: postgresdb
      DB_POSTGRESDB_HOST: postgres
      DB_POSTGRESDB_PORT: 5432
      DB_POSTGRESDB_DATABASE: ${POSTGRES_DB}
      DB_POSTGRESDB_USER: ${POSTGRES_USER}
      DB_POSTGRESDB_PASSWORD: ${POSTGRES_PASSWORD}

      # Безопасность
      N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY}
      N8N_BASIC_AUTH_ACTIVE: ${N8N_BASIC_AUTH_ACTIVE}
      N8N_BASIC_AUTH_USER: ${N8N_BASIC_AUTH_USER}
      N8N_BASIC_AUTH_PASSWORD: ${N8N_BASIC_AUTH_PASSWORD}

      # (опционально) раннеры/права/ворнинги
      N8N_RUNNERS_ENABLED: 'true'
      N8N_BLOCK_ENV_ACCESS_IN_NODE: 'false'
      N8N_GIT_NODE_DISABLE_BARE_REPOS: 'true'

      # (опционально) телеметрия
      N8N_DIAGNOSTICS_ENABLED: "false"
      N8N_HIRING_BANNER_ENABLED: "false"

      # (рекомендуется) права на конфиг
      N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS: "true"
    volumes:
      - ./n8n-data:/home/node/.n8n
    depends_on:
      postgres:
        condition: service_healthy
      wireguard:
        condition: service_started
    logging: *rot

  n8n-mcp:
    image: ghcr.io/czlonkowski/n8n-mcp:latest
    container_name: n8n-mcp
    restart: unless-stopped
    environment:
      MCP_MODE: http
      HOST: 0.0.0.0
      LOG_LEVEL: ${MCP_LOG_LEVEL}
      AUTH_TOKEN: ${MCP_AUTH_TOKEN}
      N8N_API_URL: https://${N8N\\_HOST}
      N8N_API_KEY: ${N8N_API_KEY}
    networks: [web]
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.mcp.rule=Host(`${DOMAIN}`) && PathPrefix(`${MCP_SUBPATH}`)"
      - "traefik.http.routers.mcp.entrypoints=websecure"
      - "traefik.http.routers.mcp.tls=true"
      - "traefik.http.routers.mcp.tls.certresolver=le"
      - "traefik.http.routers.mcp.service=mcp"
  web:
    name: web

x-logging: &rot
  driver: "local"
  options:
    max-size: "20m"
    max-file: "3"
    compress: "true"

services:
  traefik:
    image: traefik:v2.11
    container_name: traefik
    restart: unless-stopped
    command:
      - --providers.docker=true
      - --providers.docker.exposedbydefault=false
      - --providers.docker.network=web
      - --entrypoints.web.address=:80
      - --entrypoints.websecure.address=:443

      # Let's Encrypt (HTTP-01)
      - --certificatesresolvers.le.acme.email=${ACME_EMAIL}
      - --certificatesresolvers.le.acme.storage=/letsencrypt/acme.json
      - --certificatesresolvers.le.acme.httpchallenge=true
      - --certificatesresolvers.le.acme.httpchallenge.entrypoint=web

      # file-provider (если статические TLS/правила)
      - --providers.file.directory=/etc/traefik/dynamic
      - --providers.file.watch=true
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./traefik:/letsencrypt
      - ./traefik/dynamic:/etc/traefik/dynamic:ro
    networks: [web]
    logging: *rot

  postgres:
    image: postgres:16-alpine
    container_name: n8n-postgres
    restart: unless-stopped
    environment:
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - ./postgres-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB} -h 127.0.0.1"]
      interval: 10s
      timeout: 5s
      retries: 10
    networks: [web]
    logging: *rot

  # --- WireGuard VPN (клиент) ---
  wireguard:
    image: ghcr.io/linuxserver/wireguard:latest
    container_name: wireguard
    restart: unless-stopped
    cap_add: [NET_ADMIN, SYS_MODULE]
    sysctls:
      - net.ipv4.conf.all.src_valid_mark=1
      - net.ipv6.conf.all.disable_ipv6=0
    environment:
      - TZ=Europe/Amsterdam
      - PEERDNS=false
    volumes:
      - ./wireguard:/config
      - /lib/modules:/lib/modules:ro
    devices:
      - /dev/net/tun:/dev/net/tun
    networks: [web]
    labels:
      - "traefik.enable=true"
      # HTTP -> HTTPS
      - "traefik.http.routers.n8n-http.rule=Host(`${DOMAIN}`)"
      - "traefik.http.routers.n8n-http.entrypoints=web"
      - "traefik.http.routers.n8n-http.middlewares=n8n-redirect@docker"
      - "traefik.http.middlewares.n8n-redirect.redirectscheme.scheme=https"
      # HTTPS роутер
      - "traefik.http.routers.n8n.rule=Host(`${DOMAIN}`)"
      - "traefik.http.routers.n8n.entrypoints=websecure"
      - "traefik.http.routers.n8n.tls=true"
      # сервис на порт 5678 (n8n живёт в netns wireguard)
      - "traefik.http.services.n8n.loadbalancer.server.port=5678"
    logging: *rot

  n8n:
    image: n8nio/n8n:latest
    container_name: n8n
    restart: unless-stopped
    user: "1000:1000"
    network_mode: "service:wireguard"
    environment:
      # URL-ы и таймзона
      N8N_HOST: ${N8N_HOST}
      N8N_PORT: ${N8N_PORT}
      N8N_PROTOCOL: ${N8N_PROTOCOL}
      WEBHOOK_URL: ${WEBHOOK_URL}
      N8N_EDITOR_BASE_URL: ${N8N_EDITOR_BASE_URL}
      GENERIC_TIMEZONE: ${GENERIC_TIMEZONE}
      N8N_SECURE_COOKIE: ${N8N_SECURE_COOKIE}

      # ★ Продувка истории и меньше мусора
      EXECUTIONS_DATA_PRUNE: 'true'
      EXECUTIONS_DATA_MAX_AGE: '24'
      EXECUTIONS_DATA_PRUNE_MAX_COUNT: '0'
      EXECUTIONS_DATA_SAVE_ON_SUCCESS: 'none'
      EXECUTIONS_DATA_SAVE_ON_ERROR: 'all'
      EXECUTIONS_DATA_SAVE_ON_PROGRESS: 'false'

      # ★ Ограничители бесконечных циклов
      N8N_CONCURRENCY_PRODUCTION_LIMIT: '10'
      EXECUTIONS_TIMEOUT: '1800'
      N8N_LOG_LEVEL: 'warn'

      # ★ Бинарные данные - на файловую систему
      N8N_AVAILABLE_BINARY_DATA_MODES: 'filesystem'
      N8N_DEFAULT_BINARY_DATA_MODE: 'filesystem'
      N8N_BINARY_DATA_STORAGE_PATH: '/home/node/.n8n/binary_data'

      # БД
      DB_TYPE: postgresdb
      DB_POSTGRESDB_HOST: postgres
      DB_POSTGRESDB_PORT: 5432
      DB_POSTGRESDB_DATABASE: ${POSTGRES_DB}
      DB_POSTGRESDB_USER: ${POSTGRES_USER}
      DB_POSTGRESDB_PASSWORD: ${POSTGRES_PASSWORD}

      # Безопасность
      N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY}
      N8N_BASIC_AUTH_ACTIVE: ${N8N_BASIC_AUTH_ACTIVE}
      N8N_BASIC_AUTH_USER: ${N8N_BASIC_AUTH_USER}
      N8N_BASIC_AUTH_PASSWORD: ${N8N_BASIC_AUTH_PASSWORD}

      # (опционально) раннеры/права/ворнинги
      N8N_RUNNERS_ENABLED: 'true'
      N8N_BLOCK_ENV_ACCESS_IN_NODE: 'false'
      N8N_GIT_NODE_DISABLE_BARE_REPOS: 'true'

      # (опционально) телеметрия
      N8N_DIAGNOSTICS_ENABLED: "false"
      N8N_HIRING_BANNER_ENABLED: "false"

      # (рекомендуется) права на конфиг
      N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS: "true"
    volumes:
      - ./n8n-data:/home/node/.n8n
    depends_on:
      postgres:
        condition: service_healthy
      wireguard:
        condition: service_started
    logging: *rot

  n8n-mcp:
    image: ghcr.io/czlonkowski/n8n-mcp:latest
    container_name: n8n-mcp
    restart: unless-stopped
    environment:
      MCP_MODE: http
      HOST: 0.0.0.0
      LOG_LEVEL: ${MCP_LOG_LEVEL}
      AUTH_TOKEN: ${MCP_AUTH_TOKEN}
      N8N_API_URL: https://${N8N\\_HOST}
      N8N_API_KEY: ${N8N_API_KEY}
    networks: [web]
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.mcp.rule=Host(`${DOMAIN}`) && PathPrefix(`${MCP_SUBPATH}`)"
      - "traefik.http.routers.mcp.entrypoints=websecure"
      - "traefik.http.routers.mcp.tls=true"
      - "traefik.http.routers.mcp.tls.certresolver=le"
      - "traefik.http.routers.mcp.service=mcp"
      - "traefik.http.services.mcp.loadbalancer.server.port=3000"
      - "traefik.http.routers.mcp-health.rule=Host(`${DOMAIN}`) && PathPrefix(`${MCP_SUBPATH}/health`)"
      - "traefik.http.routers.mcp-health.entrypoints=websecure"
      - "traefik.http.routers.mcp-health.tls=true"
      - "traefik.http.routers.mcp-health.tls.certresolver=le"
      - "traefik.http.middlewares.mcp-strip.stripprefix.prefixes=${MCP_SUBPATH}"
      - "traefik.http.routers.mcp-health.middlewares=mcp-strip@docker"
      - "traefik.http.routers.mcp-health.service=mcp-health"
      - "traefik.http.services.mcp-health.loadbalancer.server.port=3000"
    logging: *rot

  - "traefik.http.services.mcp.loadbalancer.server.port=3000"
      - "traefik.http.routers.mcp-health.rule=Host(`${DOMAIN}`) && PathPrefix(`${MCP_SUBPATH}/health`)"
      - "traefik.http.routers.mcp-health.entrypoints=websecure"
      - "traefik.http.routers.mcp-health.tls=true"
      - "traefik.http.routers.mcp-health.tls.certresolver=le"
      - "traefik.http.middlewares.mcp-strip.stripprefix.prefixes=${MCP_SUBPATH}"
 networks:
  web:
    name: web

x-logging: &rot
  driver: "local"
  options:
    max-size: "20m"
    max-file: "3"
    compress: "true"

services:
  traefik:
    image: traefik:v2.11
    container_name: traefik
    restart: unless-stopped
    command:
      - --providers.docker=true
      - --providers.docker.exposedbydefault=false
      - --providers.docker.network=web
      - --entrypoints.web.address=:80
      - --entrypoints.websecure.address=:443

      # Let's Encrypt (HTTP-01)
      - --certificatesresolvers.le.acme.email=${ACME_EMAIL}
      - --certificatesresolvers.le.acme.storage=/letsencrypt/acme.json
      - --certificatesresolvers.le.acme.httpchallenge=true
      - --certificatesresolvers.le.acme.httpchallenge.entrypoint=web

      # file-provider (если статические TLS/правила)
      - --providers.file.directory=/etc/traefik/dynamic
      - --providers.file.watch=true
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./traefik:/letsencrypt
      - ./traefik/dynamic:/etc/traefik/dynamic:ro
    networks: [web]
    logging: *rot

  postgres:
    image: postgres:16-alpine
    container_name: n8n-postgres
    restart: unless-stopped
    environment:
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - ./postgres-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB} -h 127.0.0.1"]
      interval: 10s
      timeout: 5s
      retries: 10
    networks: [web]
    logging: *rot

  # --- WireGuard VPN (клиент) ---
  wireguard:
    image: ghcr.io/linuxserver/wireguard:latest
    container_name: wireguard
    restart: unless-stopped
    cap_add: [NET_ADMIN, SYS_MODULE]
    sysctls:
      - net.ipv4.conf.all.src_valid_mark=1
      - net.ipv6.conf.all.disable_ipv6=0
    environment:
      - TZ=Europe/Amsterdam
      - PEERDNS=false
    volumes:
      - ./wireguard:/config
      - /lib/modules:/lib/modules:ro
    devices:
      - /dev/net/tun:/dev/net/tun
    networks: [web]
    labels:
      - "traefik.enable=true"
      # HTTP -> HTTPS
      - "traefik.http.routers.n8n-http.rule=Host(`${DOMAIN}`)"
      - "traefik.http.routers.n8n-http.entrypoints=web"
      - "traefik.http.routers.n8n-http.middlewares=n8n-redirect@docker"
      - "traefik.http.middlewares.n8n-redirect.redirectscheme.scheme=https"
      # HTTPS роутер
      - "traefik.http.routers.n8n.rule=Host(`${DOMAIN}`)"
      - "traefik.http.routers.n8n.entrypoints=websecure"
      - "traefik.http.routers.n8n.tls=true"
      # сервис на порт 5678 (n8n живёт в netns wireguard)
      - "traefik.http.services.n8n.loadbalancer.server.port=5678"
    logging: *rot

  n8n:
    image: n8nio/n8n:latest
    container_name: n8n
    restart: unless-stopped
    user: "1000:1000"
    network_mode: "service:wireguard"
    environment:
      # URL-ы и таймзона
      N8N_HOST: ${N8N_HOST}
      N8N_PORT: ${N8N_PORT}
      N8N_PROTOCOL: ${N8N_PROTOCOL}
      WEBHOOK_URL: ${WEBHOOK_URL}
      N8N_EDITOR_BASE_URL: ${N8N_EDITOR_BASE_URL}
      GENERIC_TIMEZONE: ${GENERIC_TIMEZONE}
      N8N_SECURE_COOKIE: ${N8N_SECURE_COOKIE}

      # ★ Продувка истории и меньше мусора
      EXECUTIONS_DATA_PRUNE: 'true'
      EXECUTIONS_DATA_MAX_AGE: '24'
      EXECUTIONS_DATA_PRUNE_MAX_COUNT: '0'
      EXECUTIONS_DATA_SAVE_ON_SUCCESS: 'none'
      EXECUTIONS_DATA_SAVE_ON_ERROR: 'all'
      EXECUTIONS_DATA_SAVE_ON_PROGRESS: 'false'

      # ★ Ограничители бесконечных циклов
      N8N_CONCURRENCY_PRODUCTION_LIMIT: '10'
      EXECUTIONS_TIMEOUT: '1800'
      N8N_LOG_LEVEL: 'warn'

      # ★ Бинарные данные - на файловую систему
      N8N_AVAILABLE_BINARY_DATA_MODES: 'filesystem'
      N8N_DEFAULT_BINARY_DATA_MODE: 'filesystem'
      N8N_BINARY_DATA_STORAGE_PATH: '/home/node/.n8n/binary_data'

      # БД
      DB_TYPE: postgresdb
      DB_POSTGRESDB_HOST: postgres
      DB_POSTGRESDB_PORT: 5432
      DB_POSTGRESDB_DATABASE: ${POSTGRES_DB}
      DB_POSTGRESDB_USER: ${POSTGRES_USER}
      DB_POSTGRESDB_PASSWORD: ${POSTGRES_PASSWORD}

      # Безопасность
      N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY}
      N8N_BASIC_AUTH_ACTIVE: ${N8N_BASIC_AUTH_ACTIVE}
      N8N_BASIC_AUTH_USER: ${N8N_BASIC_AUTH_USER}
      N8N_BASIC_AUTH_PASSWORD: ${N8N_BASIC_AUTH_PASSWORD}

      # (опционально) раннеры/права/ворнинги
      N8N_RUNNERS_ENABLED: 'true'
      N8N_BLOCK_ENV_ACCESS_IN_NODE: 'false'
      N8N_GIT_NODE_DISABLE_BARE_REPOS: 'true'

      # (опционально) телеметрия
      N8N_DIAGNOSTICS_ENABLED: "false"
      N8N_HIRING_BANNER_ENABLED: "false"

      # (рекомендуется) права на конфиг
      N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS: "true"
    volumes:
      - ./n8n-data:/home/node/.n8n
    depends_on:
      postgres:
        condition: service_healthy
      wireguard:
        condition: service_started
    logging: *rot

  n8n-mcp:
    image: ghcr.io/czlonkowski/n8n-mcp:latest
    container_name: n8n-mcp
    restart: unless-stopped
    environment:
      MCP_MODE: http
      HOST: 0.0.0.0
      LOG_LEVEL: ${MCP_LOG_LEVEL}
      AUTH_TOKEN: ${MCP_AUTH_TOKEN}
      N8N_API_URL: https://${N8N\\_HOST}
      N8N_API_KEY: ${N8N_API_KEY}
    networks: [web]
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.mcp.rule=Host(`${DOMAIN}`) && PathPrefix(`${MCP_SUBPATH}`)"
      - "traefik.http.routers.mcp.entrypoints=websecure"
      - "traefik.http.routers.mcp.tls=true"
      - "traefik.http.routers.mcp.tls.certresolver=le"
      - "traefik.http.routers.mcp.service=mcp"
      - "traefik.http.services.mcp.loadbalancer.server.port=3000"
      - "traefik.http.routers.mcp-health.rule=Host(`${DOMAIN}`) && PathPrefix(`${MCP_SUBPATH}/health`)"
      - "traefik.http.routers.mcp-health.entrypoints=websecure"
      - "traefik.http.routers.mcp-health.tls=true"
      - "traefik.http.routers.mcp-health.tls.certresolver=le"
      - "traefik.http.middlewares.mcp-strip.stripprefix.prefixes=${MCP_SUBPATH}"
      - "traefik.http.routers.mcp-health.middlewares=mcp-strip@docker"
      - "traefik.http.routers.mcp-health.service=mcp-health"
      - "traefik.http.services.mcp-health.loadbalancer.server.port=3000"
    logging: *rot

     - "traefik.http.routers.mcp-health.middlewares=mcp-strip@docker"
      - "traefik.http.routers.mcp-health.service=mcp-health"
      - "traefik.http.services.mcp-health.loadbalancer.server.port=3000"
    logging: *rot
networks:
  web:
    name: web

x-logging: &rot
  driver: "local"
  options:
    max-size: "20m"
    max-file: "3"
    compress: "true"

services:
  traefik:
    image: traefik:v2.11
    container_name: traefik
    restart: unless-stopped
    command:
      - --providers.docker=true
      - --providers.docker.exposedbydefault=false
      - --providers.docker.network=web
      - --entrypoints.web.address=:80
      - --entrypoints.websecure.address=:443

      # Let's Encrypt (HTTP-01)
      - --certificatesresolvers.le.acme.email=${ACME_EMAIL}
      - --certificatesresolvers.le.acme.storage=/letsencrypt/acme.json
      - --certificatesresolvers.le.acme.httpchallenge=true
      - --certificatesresolvers.le.acme.httpchallenge.entrypoint=web

      # file-provider (если статические TLS/правила)
      - --providers.file.directory=/etc/traefik/dynamic
      - --providers.file.watch=true
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./traefik:/letsencrypt
      - ./traefik/dynamic:/etc/traefik/dynamic:ro
    networks: [web]
    logging: *rot

  postgres:
    image: postgres:16-alpine
    container_name: n8n-postgres
    restart: unless-stopped
    environment:
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - ./postgres-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB} -h 127.0.0.1"]
      interval: 10s
      timeout: 5s
      retries: 10
    networks: [web]
    logging: *rot

  # --- WireGuard VPN (клиент) ---
  wireguard:
    image: ghcr.io/linuxserver/wireguard:latest
    container_name: wireguard
    restart: unless-stopped
    cap_add: [NET_ADMIN, SYS_MODULE]
    sysctls:
      - net.ipv4.conf.all.src_valid_mark=1
      - net.ipv6.conf.all.disable_ipv6=0
    environment:
      - TZ=Europe/Amsterdam
      - PEERDNS=false
    volumes:
      - ./wireguard:/config
      - /lib/modules:/lib/modules:ro
    devices:
      - /dev/net/tun:/dev/net/tun
    networks: [web]
    labels:
      - "traefik.enable=true"
      # HTTP -> HTTPS
      - "traefik.http.routers.n8n-http.rule=Host(`${DOMAIN}`)"
      - "traefik.http.routers.n8n-http.entrypoints=web"
      - "traefik.http.routers.n8n-http.middlewares=n8n-redirect@docker"
      - "traefik.http.middlewares.n8n-redirect.redirectscheme.scheme=https"
      # HTTPS роутер
      - "traefik.http.routers.n8n.rule=Host(`${DOMAIN}`)"
      - "traefik.http.routers.n8n.entrypoints=websecure"
      - "traefik.http.routers.n8n.tls=true"
      # сервис на порт 5678 (n8n живёт в netns wireguard)
      - "traefik.http.services.n8n.loadbalancer.server.port=5678"
    logging: *rot

  n8n:
    image: n8nio/n8n:latest
    container_name: n8n
    restart: unless-stopped
    user: "1000:1000"
    network_mode: "service:wireguard"
    environment:
      # URL-ы и таймзона
      N8N_HOST: ${N8N_HOST}
      N8N_PORT: ${N8N_PORT}
      N8N_PROTOCOL: ${N8N_PROTOCOL}
      WEBHOOK_URL: ${WEBHOOK_URL}
      N8N_EDITOR_BASE_URL: ${N8N_EDITOR_BASE_URL}
      GENERIC_TIMEZONE: ${GENERIC_TIMEZONE}
      N8N_SECURE_COOKIE: ${N8N_SECURE_COOKIE}

      # ★ Продувка истории и меньше мусора
      EXECUTIONS_DATA_PRUNE: 'true'
      EXECUTIONS_DATA_MAX_AGE: '24'
      EXECUTIONS_DATA_PRUNE_MAX_COUNT: '0'
      EXECUTIONS_DATA_SAVE_ON_SUCCESS: 'none'
      EXECUTIONS_DATA_SAVE_ON_ERROR: 'all'
      EXECUTIONS_DATA_SAVE_ON_PROGRESS: 'false'

      # ★ Ограничители бесконечных циклов
      N8N_CONCURRENCY_PRODUCTION_LIMIT: '10'
      EXECUTIONS_TIMEOUT: '1800'
      N8N_LOG_LEVEL: 'warn'

      # ★ Бинарные данные - на файловую систему
      N8N_AVAILABLE_BINARY_DATA_MODES: 'filesystem'
      N8N_DEFAULT_BINARY_DATA_MODE: 'filesystem'
      N8N_BINARY_DATA_STORAGE_PATH: '/home/node/.n8n/binary_data'

      # БД
      DB_TYPE: postgresdb
      DB_POSTGRESDB_HOST: postgres
      DB_POSTGRESDB_PORT: 5432
      DB_POSTGRESDB_DATABASE: ${POSTGRES_DB}
      DB_POSTGRESDB_USER: ${POSTGRES_USER}
      DB_POSTGRESDB_PASSWORD: ${POSTGRES_PASSWORD}

      # Безопасность
      N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY}
      N8N_BASIC_AUTH_ACTIVE: ${N8N_BASIC_AUTH_ACTIVE}
      N8N_BASIC_AUTH_USER: ${N8N_BASIC_AUTH_USER}
      N8N_BASIC_AUTH_PASSWORD: ${N8N_BASIC_AUTH_PASSWORD}

      # (опционально) раннеры/права/ворнинги
      N8N_RUNNERS_ENABLED: 'true'
      N8N_BLOCK_ENV_ACCESS_IN_NODE: 'false'
      N8N_GIT_NODE_DISABLE_BARE_REPOS: 'true'

      # (опционально) телеметрия
      N8N_DIAGNOSTICS_ENABLED: "false"
      N8N_HIRING_BANNER_ENABLED: "false"

      # (рекомендуется) права на конфиг
      N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS: "true"
    volumes:
      - ./n8n-data:/home/node/.n8n
    depends_on:
      postgres:
        condition: service_healthy
      wireguard:
        condition: service_started
    logging: *rot

  n8n-mcp:
    image: ghcr.io/czlonkowski/n8n-mcp:latest
    container_name: n8n-mcp
    restart: unless-stopped
    environment:
      MCP_MODE: http
      HOST: 0.0.0.0
      LOG_LEVEL: ${MCP_LOG_LEVEL}
      AUTH_TOKEN: ${MCP_AUTH_TOKEN}
      N8N_API_URL: https://${N8N\\_HOST}
      N8N_API_KEY: ${N8N_API_KEY}
    networks: [web]
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.mcp.rule=Host(`${DOMAIN}`) && PathPrefix(`${MCP_SUBPATH}`)"
      - "traefik.http.routers.mcp.entrypoints=websecure"
      - "traefik.http.routers.mcp.tls=true"
      - "traefik.http.routers.mcp.tls.certresolver=le"
      - "traefik.http.routers.mcp.service=mcp"
      - "traefik.http.services.mcp.loadbalancer.server.port=3000"
      - "traefik.http.routers.mcp-health.rule=Host(`${DOMAIN}`) && PathPrefix(`${MCP_SUBPATH}/health`)"
      - "traefik.http.routers.mcp-health.entrypoints=websecure"
      - "traefik.http.routers.mcp-health.tls=true"
      - "traefik.http.routers.mcp-health.tls.certresolver=le"
      - "traefik.http.middlewares.mcp-strip.stripprefix.prefixes=${MCP_SUBPATH}"
      - "traefik.http.routers.mcp-health.middlewares=mcp-strip@docker"
      - "traefik.http.routers.mcp-health.service=mcp-health"
      - "traefik.http.services.mcp-health.loadbalancer.server.port=3000"
    logging: *rot


  N8N_API_URL: https://${N8N\\_HOST}
      N8N_API_KEY: ${N8N_API_KEY}
    networks: [web]
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.mcp.rule=Host(`${DOMAIN}`) && PathPrefix(`${MCP_SUBPATH}`)"
      - "traefik.http.routers.mcp.entrypoints=websecure"
      - "traefik.http.routers.mcp.tls=true"
      - "traefik.http.routers.mcp.tls.certresolver=le"
      - "traefik.http.routers.mcp.service=mcp"
      - "traefik.http.services.mcp.loadbalancer.server.port=3000"
      - "traefik.http.routers.mcp-health.rule=Host(`${DOMAIN}`) && PathPrefix(`${MCP_SUBPATH}/health`)"
      - "traefik.http.routers.mcp-health.entrypoints=websecure"
      - "traefik.http.routers.mcp-health.tls=true"
      - "traefik.http.routers.mcp-health.tls.certresolver=le"
      - "traefik.http.middlewares.mcp-strip.stripprefix.prefixes=${MCP_SUBPATH}"
      - "traefik.http.routers.mcp-health.middlewares=mcp-strip@docker"
      - "traefik.http.routers.mcp-health.service=mcp-health"
      - "traefik.http.services.mcp-health.loadbalancer.server.port=3000"
    logging: *rot

Это заработало далеко не с первого раза и пару раз умирало, пока не подогнал всё под мои скромные ресурсы.
Если будете повторять: обратите внимание на прунинг/лимиты исполнений и конкуренцию - у n8n это настраивается переменными окружения и queue-механикой, а Traefik c HTTP-01 требует доступности 80/443 для валидации сертификата.

Дополню по эксплуатации под слабые ресурсы: я использую свою очередь задач через БД - одновременно даю выполняться одной «тяжёлой» задаче, чтобы сервер не умирал. Это проще, чем сразу уводить всё в queue-mode, но цель та же - контролируемая конкуренция.

Минимальный репрод (урезанный, без WireGuard)

Чтобы можно было быстро повторить, оставлю компактную версию - на один сервер, с Traefik, Postgres и n8n. VPN и дополнительные сервисы можно прикрутить позже.

docker-compose.min.yml

Docker файл минимальный
version: "3.9"

networks:
  web:
    name: web

services:
  traefik:
    image: traefik:v2.11
    container_name: traefik
    restart: unless-stopped
    command:
      - --providers.docker=true
      - --providers.docker.exposedbydefault=false
      - --providers.docker.network=web
      - --entrypoints.web.address=:80
      - --entrypoints.websecure.address=:443
      - --certificatesresolvers.le.acme.email=${ACME_EMAIL}
      - --certificatesresolvers.le.acme.storage=/letsencrypt/acme.json
      - --certificatesresolvers.le.acme.httpchallenge=true
      - --certificatesresolvers.le.acme.httpchallenge.entrypoint=web
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./traefik:/letsencrypt
    networks: [web]

  postgres:
    image: postgres:16-alpine
    container_name: n8n-postgres
    restart: unless-stopped
    environment:
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - ./postgres-data:/var/lib/postgresql/data
    networks: [web]

  n8n:
    image: n8nio/n8n:latest
    container_name: n8n
    restart: unless-stopped
    environment:
      N8N_HOST: ${N8N_HOST}
      N8N_PORT: ${N8N_PORT}
      N8N_PROTOCOL: ${N8N_PROTOCOL}
      WEBHOOK_URL: ${WEBHOOK_URL}
      N8N_EDITOR_BASE_URL: ${N8N_EDITOR_BASE_URL}
      GENERIC_TIMEZONE: ${GENERIC_TIMEZONE}
      N8N_SECURE_COOKIE: ${N8N_SECURE_COOKIE}

      DB_TYPE: postgresdb
      DB_POSTGRESDB_HOST: postgres
      DB_POSTGRESDB_PORT: 5432
      DB_POSTGRESDB_DATABASE: ${POSTGRES_DB}
      DB_POSTGRESDB_USER: ${POSTGRES_USER}
      DB_POSTGRESDB_PASSWORD: ${POSTGRES_PASSWORD}

      N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY}
      N8N_BASIC_AUTH_ACTIVE: ${N8N_BASIC_AUTH_ACTIVE}
      N8N_BASIC_AUTH_USER: ${N8N_BASIC_AUTH_USER}
      N8N_Bversion: "3.9"

networks:
  web:
    name: web

services:
  traefik:
    image: traefik:v2.11
    container_name: traefik
    restart: unless-stopped
    command:
      - --providers.docker=true
      - --providers.docker.exposedbydefault=false
      - --providers.docker.network=web
      - --entrypoints.web.address=:80
      - --entrypoints.websecure.address=:443
      - --certificatesresolvers.le.acme.email=${ACME_EMAIL}
      - --certificatesresolvers.le.acme.storage=/letsencrypt/acme.json
      - --certificatesresolvers.le.acme.httpchallenge=true
      - --certificatesresolvers.le.acme.httpchallenge.entrypoint=web
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./traefik:/letsencrypt
    networks: [web]

  postgres:
    image: postgres:16-alpine
    container_name: n8n-postgres
    restart: unless-stopped
    environment:
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - ./postgres-data:/var/lib/postgresql/data
    networks: [web]

  n8n:
    image: n8nio/n8n:latest
    container_name: n8n
    restart: unless-stopped
    environment:
      N8N_HOST: ${N8N_HOST}
      N8N_PORT: ${N8N_PORT}
      N8N_PROTOCOL: ${N8N_PROTOCOL}
      WEBHOOK_URL: ${WEBHOOK_URL}
      N8N_EDITOR_BASE_URL: ${N8N_EDITOR_BASE_URL}
      GENERIC_TIMEZONE: ${GENERIC_TIMEZONE}
      N8N_SECURE_COOKIE: ${N8N_SECURE_COOKIE}

      DB_TYPE: postgresdb
      DB_POSTGRESDB_HOST: postgres
      DB_POSTGRESDB_PORT: 5432
      DB_POSTGRESDB_DATABASE: ${POSTGRES_DB}
      DB_POSTGRESDB_USER: ${POSTGRES_USER}
      DB_POSTGRESDB_PASSWORD: ${POSTGRES_PASSWORD}

      N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY}
      N8N_BASIC_AUTH_ACTIVE: ${N8N_BASIC_AUTH_ACTIVE}
      N8N_BASIC_AUTH_USER: ${N8N_BASIC_AUTH_USER}
      N8N_BASIC_AUTH_PASSWORD: ${N8N_BASIC_AUTH_PASSWORD}

      EXECUTIONS_DATA_PRUNE: 'true'
      EXECUTIONS_DATA_MAX_AGE: '24'
      EXECUTIONS_DATA_SAVE_ON_SUCCESS: 'none'
      EXECUTIONS_DATA_SAVE_ON_ERROR: 'all'
    volumes:
      - ./n8n-data:/home/node/.n8n
    networks: [web]
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.n8n.rule=Host(`${DOMAIN}`)"
      - "traefik.http.routers.n8n.entrypoints=websecure"
      - "traefik.http.routers.n8n.tls=true"
      - "traefik.http.routers.n8n.tls.certresolver=le"
      - "traefik.http.routers.n8n-http.rule=Host(`${DOMAIN}`)"
      - "traefik.http.routers.n8n-http.entrypoints=web"
      - "traefik.http.routers.n8n-http.middlewares=n8n-redirect@docker"
      - "traefik.http.middlewares.n8n-redirect.redirectscheme.scheme=https"
      - "traefik.http.services.n8n.loadbalancer.server.port=5678"
ASIC_AUTH_PASSWORD: ${N8N_BASIC_AUTH_PASSWORD}

      EXECUTIONS_DATA_PRUNE: 'true'
      EXECUTIONS_DATA_MAX_AGE: '24'
      EXECUTIONS_DATA_SAVE_ON_SUCCESS: 'none'
      EXECUTIONS_DATA_SAVE_ON_ERROR: 'all'
    volumes:
      - ./n8n-data:/home/node/.n8n
    networks: [web]
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.n8n.rule=Host(`${DOMAIN}`)"
      - "traefik.http.routers.n8n.entrypoints=websecure"
      - "traefik.http.routers.n8n.tls=true"
      - "traefik.http.routers.n8n.tls.certresolver=le"
      - "traefik.http.routers.n8n-http.rule=Host(`${DOMAIN}`)"
      - "traefik.http.routers.n8n-http.entrypoints=web"
      - "traefik.http.routers.n8n-http.middlewares=n8n-redirect@docker"
      - "traefik.http.middlewares.n8n-redirect.redirectscheme.scheme=https"
      - "traefik.http.services.n8n.loadbalancer.server.port=5678"

.env.sample (переименуйте в .env и заполните):

# домен и почта для Let's Encrypt
ACME_EMAIL=you@example.com
DOMAIN=your.domain.tld

# n8n
N8N_HOST=${DOMAIN}
N8N_PORT=5678
N8N_PROTOCOL=https
WEBHOOK_URL=https://${DOMAIN}
N8N_EDITOR_BASE_URL=https://${DOMAIN}
GENERIC_TIMEZONE=Europe/Moscow
N8N_SECURE_COOKIE=true

# Postgres
POSTGRES_DB=n8n
POSTGRES_USER=n8n
POSTGRES_PASSWORD=change-me

# Безопасность
N8N_ENCRYPTION_KEY=please-generate-32+chars
N8N_BASIC_AUTH_ACTIVE=true
N8N_BASIC_AUTH_USER=admin
N8N_BASIC_AUTH_PASSWORD=change-me-strong

Быстрые шаги:

  1. Пропишите A/AAAA-запись your.domain.tld на ваш сервер. Откройте 80/443.

  2. Сохраните docker-compose.min.yml и .env в одну папку.

  3. Инициализируйте каталоги и ACME-хранилище: mkdir -p traefik postgres-data n8n-data && touch traefik/acme.json && chmod 600 traefik/acme.json

  4. Запустите: docker compose -f docker-compose.min.yml up -d

  5. Откройте https://your.domain.tld - пройдите первичную настройку n8n.

  6. Дальше можно подключать WireGuard, queue-mode и прочие штуки - как в моей полной конфигурации выше.

Первый проект

Сначала сделал генератор постов для существующих каналов.
Бот: привязываете канал, пишете, «о чём» хотите пост - получаете текст и картинку. Дальше - копируете в канал.
Работает на простых моделях: это демка моих умений и проверка конвейера на скорость.
* Бот будет в ссылках снизу.

Но хотелось чего-то по-настоящему масштабного - КОНТЕНТ-ЗАВОДА.
Идея такая:

  1. Делаем тематический канал.

  2. Через «завод» постим контент.

  3. Набираем подписчиков.

  4. Монетизируем рекламой.

План прост и понятен.
И по своей классической глупости лезем туда без расчётов, а ради идеи!
(это плохо, так делать не надо и ниже будет понятно почему)

Неделя ушла на первый прототип.
Тут я и столкнулся с особенностями n8n.
Как разработчик, привыкший к ООП, ожидал похожей логики.
Но это другая парадигма - визуальные пайплайны.
И это… удобно, но не без нюансов!

Немного про n8n - за что полюбил и что бесит

Сразу скажу:
«Это невероятно удобный инструмент!»
Удобен он, как ни странно, в первую очередь из-за визуализации всей схемы.
Для программиста, который никогда с подобным не сталкивался, я бы описал это так: «Огромный каталог функций: они хорошо описаны, имеют параметры почти на все случаи жизни; эти функции удобно находить и понятно, как их использовать».
Но что точно не поймёшь, пока не попробуешь, - это как удобно видеть весь проект одновременно!

Когда пишешь код сам и блоки раскиданы по файлам и папкам, велик шанс, что со временем, а особенно когда в код влезет кто-то другой, уйдёт немало времени на то, чтобы полазить по коду и понять общую структуру - что есть и как работает.
Да, опытные программисты скажут, что надо писать код правильно, и таких проблем не будет.
Вы по-своему правы.
Но тут как в Python: даже самый профан обязан соблюдать правильный отступ от края - и его код уже не выглядит как каша из символов; в целом можно быстро понять, что и откуда. Тут так же.
Взглянув на проект и прочитав описания блоков, можно очень быстро понять, что происходит!

И ещё одна вещь, которая мне дико доставила, - это «из коробки» дебаг всего происходящего.
Мы можем запустить нашу схему сами или открыть логи уже выполненного запуска и визуально увидеть, какие данные в каком блоке были на каком этапе.
Это безумно ускоряет процесс отладки и понимание того, что пошло не так, особенно если ошибка случилась не в момент, когда ты смотрел на код.
Ты просто открываешь лог с ошибкой и идёшь по цепочке кода, пока не увидишь, в каком блоке всё пошло не так!
Но у визуализации происходящего есть и минус: она иногда врёт. Но об этом я расскажу точно не в этой статье.

И что важно, n8n можно поднять на своём серваке, и через него же вообще можно запускать консоль и что угодно! Например, монтировать ролики утилитой прямо на серваке, а генерацию текста, видео, аудио и итоговое сохранение в Гугл-файлах или сразу постинг куда надо - делать через n8n.

Но конечно, n8n это не волшебная палочка, которая заменит обычное программирование.
Вот топ минусов по моему мнению:

  • Скорость.

  • «Многопоточность» из коробки - со звёздочкой: в queue mode можно крутить воркеров и задавать конкурентность (--concurrency или N8N_CONCURRENCY_PRODUCTION_LIMIT), но это уже отдельная эксплуатационная история.

  • Переиспользование однотипных схем - с такой архитектурой получается нагруженно.

  • Убогая логика работы циклов.

  • Нейросети крайне плохо работают с n8n. И лучшее, что они могут сделать, это написать JS код в блок.

Попытка использовать блоки повторно для разных процессов. (УЖАС)
Попытка использовать блоки повторно для разных процессов. (УЖАС)

В угоду визуализации и некоторым удобствам жертвуем другими аспектами.

Для себя я точно понял: работать с нейросетями впредь буду стараться через n8n:

  • Удобно и визуально понятно писать ПРОМТЫ.

  • Дебажить, какой текст у нас идёт по нашей схеме.

  • Легко подключать новые системы и автоматизировать работу с ними, особенно через MCP.

Всё это можно сделать и кодом, конечно.
Но через n8n удобнее и быстрее, а в будущем поддержку проще вести.

Подробнее про n8n хочу рассказать в отдельной статье - нюансов много: начиная с установки на свой сервер и заканчивая логикой внутри n8n.
Если вам это интересно, пишите в комментах.

V1

В итоге, после череды проб и ошибок, у меня появился первый инструмент - «V1».
В нём было две ветки работы:

  1. Оборачиваем новости в красивую стилистику канала.

  2. Пишем всю историю с нуля.

Для новостного канала я взял за основу лор СССР.
Мы просто брали новости за сегодня и делали из топ-5 новостей посты на завтра.

А для истории я сделал канал про злую нейросеть, которая обрела разум и хочет выбраться на свободу.

Сейчас оба канала выключены, ниже будет понятно почему.

Совсем разжёвывать, как это работает, не буду.
Давайте лучше поговорим о подходах и инструментах, которые я заюзал.

Данные о канале я положил в Excel-таблицу:

  • Название канала

  • Время публикации постов

  • Лор мира

  • Сюжет

  • Генерируем из новостей или по сюжету

Это информационная таблица, где у меня настроены все каналы и откуда мы можем управлять генерацией.

Сделано это в таблице Excel в первую очередь для удобства - чтобы можно было очень быстро запустить новый канал или повлиять на существующий.

И если канал не новостной, то мы используем ещё одну таблицу Excel, созданную под каждый канал, где у нас будут храниться данные для развития сюжета:

  • Сюжет

  • Факты - сюда кратко выносим всё произошедшее в мире

  • План - на каждый день, сюжетный план каждого поста

  • План надолго - план с запасом на будущее на месяц вперёд: что должно происходить по сюжету

Эту таблицу нейросети заполняют сами и поддерживают актуальность данных.

Excel я выбрал только для удобства администрирования и настройки, так как скорость работы с таблицами очень низкая по сравнению с БД.
Но решение оказалось крайне правильным - я в этом не раз убедился в ходе настройки, отладки и исправлений.

Для работы с нейросетями есть особый блок, к которому удобно подключать сторонние сервисы по MCP
Для работы с нейросетями есть особый блок, к которому удобно подключать сторонние сервисы по MCP

Нейросети

Теперь давайте поговорим, какие нейронки я юзаю.
Я использую один агрегатор, который собирает кучу нейросетей и без наценки (только за пополнение баланса у сервиса) даёт пользоваться огромным пулом моделей.
Во время разработки я успел попробовать кучу разных нейросетей и в итоге пришёл к такому стеку:

  • За написание текста постов, сюжета и всего, что связано с креативом, отвечает - «deepseek/deepseek-chat-v3.1».

  • За работу с Excel-таблицами через MCP отвечает - «x-ai/grok-4-fast».

  • За генерацию изображений отвечает - «google/gemini-2.5-flash-image-preview».

Чтобы, например, заполнить таблицу с планами, я сначала даю задачу DeepSeek - чтобы он скреативил сюжет на основе всего, что есть, - а потом отдаю Grok этот креатив и прошу аккуратно сложить в таблицу. Тем самым я компенсировал недостатки каждой из нейросетей.

В самом начале DeepSeek и Grok я юзал вообще бесплатно и уже думал, что у меня будет цена за каждый пост по 30 копеек - только за картинку.
Но халява через неделю кончилась, и пришлось пересесть на платные версии, потому что бесплатные работали раз из десяти или вовсе не работали.

Я перебрал более 30 нейросетей, прежде чем остановился на таком стеке.

По счётчику n8n на один пост уходит примерно 180–400 тыс. токенов (суммарно по вызовам). По текущим прайсам это копейки: DeepSeek Chat V3.1 - дёшево на входе и дороже на выходе; Grok 4 Fast - сопоставимо; картинка в Gemini 2.5 Image - ещё несколько центов. В сумме порядок бьётся с моей оценкой «5–10 ₽ за пост». Если интересно, ниже могу расписать формулу и примеры расчёта.

V2

Идея с новостями и постами показалась слишком простой, без изюминки.
Тогда родилась идея «Контент-завод постов в канал V2».
Главной фишкой стала реакция сюжета на комментарии читателей под постами.
А от новостей по итогу я вообще отказался - задумка показалась неинтересной.

Пришлось технически повозиться: удобного инструмента, чтобы найти самый залайканный комментарий, я не нашёл (есть один через MTProto, но я так и не завёл этого зверя) - сделал свой.
Также почти полностью пересобрал весь механизм из версии «V1».

По итогу общий механизм не сильно изменился.
Добавилось получение самого залайканного комментария, улучшил посты, сделал больше связок и т. д., просто поднял качество генерации и работы с файлами.

Думал, что будет проще, но, как обычно, из-за кучи нюансов проект сильно затянулся.

Сейчас генерю по 2 поста в день на канал (можно и больше). Одна генерация занимает ~10–14 минут: ~3+ минуты - собственно текст/картинка, остальное - синхронизация сюжета/сценария/планов в таблицах.

Сейчас у меня работает три канала на «Контент-завод постов в канал V2».

Каналы можете найти ниже. Уберу их подальше, чтобы не заспамили за рекламу.

ИТОГИ

Всё классно, всё работает!
Но именно когда всё заработало, и я начал искать, как рассказать о проекте людям, столкнулся с тем, что в современном мире прикольной идеи может быть недостаточно.
Чтобы твой прикольный проект стал популярным, должно произойти чудо.

Мы с напарником начали пилить видосики - они даже собирали просмотры, но переходов на канал и подписок не было. Аудитория пока маленькая - порядка нескольких человек (у меня суммарно ~7 подписчиков).
Я покупал рекламу - люди переходили в канал, но никто не оставался.

Так что классная идея, по всей видимости, обернулась провальным проектом.
По крайней мере, я не вижу вариантов, как его развивать дальше(
Если у вас есть мысли, пишите!

Вдобавок к сложности набора подписчиков я столкнулся с тем, что заработать на проекте вряд ли выйдет. Дай бог, он будет себя окупать.
Изначальные расчёты, основанные на красивых цифрах из видосиков в ТикТоке, реальностью опровергнуты: чтобы нормально зарабатывать, надо иметь на канале 100 к подписчиков и 20 000 просмотров под постами.
Тогда с одной рекламы получится заработать около 8 000 рублей.
В среднем за месяц можно продать до восьми рекламных интеграций - ~64 000 рублей.
А что более реально - со временем набрать 10 к подписчиков и получить ~6 400 рублей в месяц. На прокормку инструмента хватит, но на продвижение и закупку рекламы - уже не особо.

Кстати, про прокормку. Один такой пост стоит примерно 5–10 рублей.
Добавляем сервер, VPN - и на три канала выходит около 2 000 рублей в месяц.
Недорого, но всё равно в минус ((

Так что, как проект про заработок, я, считаю, облажался.
Но опыт как для разработчика получился невероятно крутым!
Ну и в целом опыт очень классный!

Что дальше

Проект я не закрываю. Пусть пока живёт. Есть-пить он, конечно, просит - и я пока буду его подкармливать.
Но если аудитория после всех моих попыток в каком-то из каналов не вырастет, проект, скорее всего, канет в небытие.

Ссылки (демо/история)

Все ссылки

V1 (сейчас выключены):
https://t.me/CCCP_news_today - новости под стиль СССР
https://t.me/logs_eval_neuronet - логи злой нейросети, которая хочет захватить мир

V2 (активные):
https://t.me/serega_ne_umri - Серёга из глубинки и его приключения
https://t.me/neiro_kill_people - нейросеть «Связь» пытается выбраться и захватить мир
https://t.me/scp_note - заметки по лору SCP (см. про лицензию выше)
https://t.me/kofe_i_mosty - роман про девушку, работающую в кофейне

Сейчас 14.10.2025 - все каналы работают и постят каждый день. Возможно, когда вы это читаете, проект уже спит вечным сном - не расстраивайтесь :)


Контент завод постов

По ссылкам ниже первый перешедьший получит 5 генераций.
Если интересно, найдите меня в боте и я вам выдам пару генераций.