networks: proxy: name: proxy internal: name: internal volumes: traefik_letsencrypt: n8n_data: postgres_data: redis_data: portainer_data: gitea_data: dify_api_storage: dify_plugin_storage: dify_weaviate_data: dify_sandbox_deps: services: traefik: image: traefik:latest container_name: traefik restart: unless-stopped ports: - "80:80" - "443:443" command: - --providers.docker=true - --providers.docker.exposedbydefault=false - --providers.docker.network=proxy - --entrypoints.web.address=:80 - --entrypoints.web.http.redirections.entrypoint.to=websecure - --entrypoints.web.http.redirections.entrypoint.scheme=https - --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 - --log.level=INFO - --accesslog=true volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - traefik_letsencrypt:/letsencrypt networks: - proxy postgres: image: postgres:16-alpine container_name: n8n-postgres restart: unless-stopped environment: POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_DB: ${POSTGRES_DB} volumes: - postgres_data:/var/lib/postgresql/data - ./postgres/init:/docker-entrypoint-initdb.d:ro networks: - internal healthcheck: test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"] interval: 10s timeout: 5s retries: 10 redis: image: redis:7-alpine container_name: n8n-redis restart: unless-stopped command: ["redis-server", "--requirepass", "${REDIS_PASSWORD}", "--appendonly", "yes"] volumes: - redis_data:/data networks: - internal healthcheck: test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"] interval: 10s timeout: 5s retries: 10 n8n: image: n8nio/n8n:latest container_name: n8n restart: unless-stopped environment: - N8N_HOST=${N8N_HOST} - N8N_PORT=5678 - N8N_LISTEN_ADDRESS=0.0.0.0 - N8N_PROTOCOL=https - N8N_EDITOR_BASE_URL=https://${N8N_HOST}/ - WEBHOOK_URL=https://${N8N_HOST}/ - GENERIC_TIMEZONE=${TIMEZONE} - TZ=${TIMEZONE} - N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY} - 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_RUNNERS_ENABLED=true - N8N_PROXY_HOPS=1 - N8N_CACHE_BACKEND=redis - QUEUE_BULL_REDIS_HOST=redis - QUEUE_BULL_REDIS_PORT=6379 - QUEUE_BULL_REDIS_PASSWORD=${REDIS_PASSWORD} - HTTP_PROXY=http://xray:8080 - HTTPS_PROXY=http://xray:8080 - NO_PROXY=localhost,127.0.0.1,postgres,redis - N8N_DIAGNOSTICS_ENABLED=false - N8N_VERSION_NOTIFICATIONS_ENABLED=false - N8N_TEMPLATES_ENABLED=false - EXTERNAL_FRONTEND_HOOKS_URLS= - N8N_DIAGNOSTICS_CONFIG_FRONTEND= - N8N_DIAGNOSTICS_CONFIG_BACKEND= volumes: - n8n_data:/home/node/.n8n networks: - proxy - internal depends_on: postgres: condition: service_healthy redis: condition: service_healthy labels: - traefik.enable=true - traefik.docker.network=proxy - traefik.http.routers.n8n.rule=Host(`${N8N_HOST}`) - traefik.http.routers.n8n.entrypoints=websecure - traefik.http.routers.n8n.tls=true - traefik.http.routers.n8n.tls.certresolver=le - traefik.http.services.n8n.loadbalancer.server.port=5678 tunnel: image: jnovack/autossh:latest container_name: n8n-tunnel restart: unless-stopped environment: SSH_REMOTE_USER: tunnel SSH_REMOTE_HOST: 94.247.214.20 SSH_REMOTE_PORT: 22 SSH_TUNNEL_PORT: 5678 SSH_TARGET_HOST: n8n SSH_TARGET_PORT: 5678 SSH_MODE: "-R" volumes: - ./ssh/tunnel_key:/id_rsa:ro networks: - internal xray: image: ghcr.io/xtls/xray-core:latest container_name: xray restart: unless-stopped volumes: - ./xray/config.json:/etc/xray/config.json:ro networks: - internal command: ["-config", "/etc/xray/config.json"] gitea: image: gitea/gitea:latest container_name: gitea restart: unless-stopped environment: - USER_UID=1000 - USER_GID=1000 - GITEA__server__DOMAIN=${GITEA_HOST} - GITEA__server__ROOT_URL=https://${GITEA_HOST}/ - GITEA__server__SSH_DOMAIN=${GITEA_HOST} - GITEA__server__SSH_PORT=222 - GITEA__server__SSH_LISTEN_PORT=22 - GITEA__database__DB_TYPE=postgres - GITEA__database__HOST=postgres:5432 - GITEA__database__NAME=gitea - GITEA__database__USER=gitea - GITEA__database__PASSWD=${GITEA_DB_PASSWORD} - GITEA__service__DISABLE_REGISTRATION=true volumes: - gitea_data:/data - /etc/timezone:/etc/timezone:ro - /etc/localtime:/etc/localtime:ro ports: - "222:22" networks: - proxy - internal depends_on: postgres: condition: service_healthy labels: - traefik.enable=true - traefik.docker.network=proxy - traefik.http.routers.gitea.rule=Host(`${GITEA_HOST}`) - traefik.http.routers.gitea.entrypoints=websecure - traefik.http.routers.gitea.tls=true - traefik.http.routers.gitea.tls.certresolver=le - traefik.http.services.gitea.loadbalancer.server.port=3000 portainer: image: portainer/portainer-ce:latest container_name: portainer restart: unless-stopped command: -H unix:///var/run/docker.sock volumes: - /var/run/docker.sock:/var/run/docker.sock - portainer_data:/data networks: - proxy labels: - traefik.enable=true - traefik.docker.network=proxy - traefik.http.routers.portainer.rule=Host(`${PORTAINER_HOST}`) - traefik.http.routers.portainer.entrypoints=websecure - traefik.http.routers.portainer.tls=true - traefik.http.routers.portainer.tls.certresolver=le - traefik.http.services.portainer.loadbalancer.server.port=9000 # --- Dify ----------------------------------------------------------------- # LLM-app платформа. БД и redis — общие (postgres: база dify/dify_plugin, # redis: db 2/3). Vector store — отдельный weaviate. Внешний вход — traefik с # path-роутингом (вместо штатного dify-nginx): /console/api,/api,/v1,/files → api, # /e/ → plugin_daemon, остальное → web. dify-api: image: langgenius/dify-api:${DIFY_VERSION} container_name: dify-api restart: unless-stopped environment: &dify-api-env MODE: api LOG_LEVEL: INFO SECRET_KEY: ${DIFY_SECRET_KEY} DEPLOY_ENV: PRODUCTION MIGRATION_ENABLED: "true" CONSOLE_API_URL: https://${DIFY_HOST} CONSOLE_WEB_URL: https://${DIFY_HOST} SERVICE_API_URL: https://${DIFY_HOST} APP_API_URL: https://${DIFY_HOST} APP_WEB_URL: https://${DIFY_HOST} FILES_URL: https://${DIFY_HOST} # --- Endpoint/Trigger плагины (вебхуки) --- # endpoint-плагины обслуживает plugin-daemon (/e/), триггеры — api (/triggers). # Без этого Dify генерирует webhook-URL на localhost (триггер не добавить). ENDPOINT_URL_TEMPLATE: https://${DIFY_HOST}/e/{hook_id} TRIGGER_URL: https://${DIFY_HOST} # --- Postgres (общий) --- DB_USERNAME: ${POSTGRES_USER} DB_PASSWORD: ${POSTGRES_PASSWORD} DB_HOST: postgres DB_PORT: "5432" DB_DATABASE: dify # --- Redis (общий) --- REDIS_HOST: redis REDIS_PORT: "6379" REDIS_PASSWORD: ${REDIS_PASSWORD} REDIS_DB: "2" CELERY_BROKER_URL: redis://:${REDIS_PASSWORD}@redis:6379/3 # --- Vector store --- VECTOR_STORE: weaviate WEAVIATE_ENDPOINT: http://dify-weaviate:8080 WEAVIATE_API_KEY: ${DIFY_WEAVIATE_API_KEY} # --- Storage (локальный том) --- STORAGE_TYPE: opendal OPENDAL_SCHEME: fs OPENDAL_FS_ROOT: storage # --- Code execution sandbox --- CODE_EXECUTION_ENDPOINT: http://dify-sandbox:8194 CODE_EXECUTION_API_KEY: ${DIFY_SANDBOX_API_KEY} # --- SSRF proxy --- SSRF_PROXY_HTTP_URL: http://dify-ssrf-proxy:3128 SSRF_PROXY_HTTPS_URL: http://dify-ssrf-proxy:3128 # ПРИМ.: исходящий прокси (xray) НЕ ставим на api/worker — это тормозит # веб-API (вызовы уходят в VLESS-туннель, блокируют воркеров gunicorn, # редактор отваливается по таймауту). Прокси только на plugin-daemon, # где крутятся LLM-провайдеры — этого достаточно для гео-разблока. # --- Plugin daemon --- PLUGIN_DAEMON_URL: http://dify-plugin-daemon:5002 PLUGIN_DAEMON_KEY: ${DIFY_PLUGIN_DAEMON_KEY} INNER_API_KEY_FOR_PLUGIN: ${DIFY_INNER_API_KEY} MARKETPLACE_ENABLED: "true" MARKETPLACE_API_URL: https://marketplace.dify.ai volumes: - dify_api_storage:/app/api/storage networks: - proxy - internal depends_on: postgres: condition: service_healthy redis: condition: service_healthy dify-weaviate: condition: service_started labels: - traefik.enable=true - traefik.docker.network=proxy - traefik.http.routers.dify-api.rule=Host(`${DIFY_HOST}`) && (PathPrefix(`/console/api`) || PathPrefix(`/api`) || PathPrefix(`/v1`) || PathPrefix(`/files`) || PathPrefix(`/triggers`)) - traefik.http.routers.dify-api.priority=10 - traefik.http.routers.dify-api.entrypoints=websecure - traefik.http.routers.dify-api.tls=true - traefik.http.routers.dify-api.tls.certresolver=le - traefik.http.routers.dify-api.service=dify-api - traefik.http.services.dify-api.loadbalancer.server.port=5001 dify-worker: image: langgenius/dify-api:${DIFY_VERSION} container_name: dify-worker restart: unless-stopped environment: <<: *dify-api-env MODE: worker volumes: - dify_api_storage:/app/api/storage networks: - internal depends_on: postgres: condition: service_healthy redis: condition: service_healthy dify-web: image: langgenius/dify-web:${DIFY_VERSION} container_name: dify-web restart: unless-stopped environment: # Контейнер в двух сетях (proxy+internal); без этого Next.js слушает только # на IP internal-сети и traefik (proxy) получает 502. Биндим все интерфейсы. HOSTNAME: 0.0.0.0 CONSOLE_API_URL: https://${DIFY_HOST} APP_API_URL: https://${DIFY_HOST} MARKETPLACE_API_URL: https://marketplace.dify.ai MARKETPLACE_URL: https://marketplace.dify.ai networks: - proxy - internal labels: - traefik.enable=true - traefik.docker.network=proxy - traefik.http.routers.dify-web.rule=Host(`${DIFY_HOST}`) - traefik.http.routers.dify-web.priority=1 - traefik.http.routers.dify-web.entrypoints=websecure - traefik.http.routers.dify-web.tls=true - traefik.http.routers.dify-web.tls.certresolver=le - traefik.http.routers.dify-web.service=dify-web - traefik.http.services.dify-web.loadbalancer.server.port=3000 dify-plugin-daemon: image: langgenius/dify-plugin-daemon:${DIFY_PLUGIN_DAEMON_VERSION} container_name: dify-plugin-daemon restart: unless-stopped environment: DB_USERNAME: ${POSTGRES_USER} DB_PASSWORD: ${POSTGRES_PASSWORD} DB_HOST: postgres DB_PORT: "5432" DB_DATABASE: dify_plugin REDIS_HOST: redis REDIS_PORT: "6379" REDIS_PASSWORD: ${REDIS_PASSWORD} REDIS_DB: "2" SERVER_PORT: "5002" SERVER_KEY: ${DIFY_PLUGIN_DAEMON_KEY} DIFY_INNER_API_URL: http://dify-api:5001 DIFY_INNER_API_KEY: ${DIFY_INNER_API_KEY} PLUGIN_REMOTE_INSTALLING_HOST: 0.0.0.0 PLUGIN_REMOTE_INSTALLING_PORT: "5003" PLUGIN_WORKING_PATH: /app/storage/cwd PYTHON_ENV_INIT_TIMEOUT: "120" PLUGIN_MAX_EXECUTION_TIMEOUT: "600" PLUGIN_STORAGE_TYPE: local PLUGIN_STORAGE_LOCAL_ROOT: /app/storage FORCE_VERIFYING_SIGNATURE: "true" MARKETPLACE_ENABLED: "true" MARKETPLACE_API_URL: https://marketplace.dify.ai # --- Исходящий прокси через xray (LLM-провайдеры крутятся здесь) --- HTTP_PROXY: ${DIFY_HTTP_PROXY} HTTPS_PROXY: ${DIFY_HTTP_PROXY} NO_PROXY: ${DIFY_NO_PROXY} volumes: - dify_plugin_storage:/app/storage networks: - proxy - internal depends_on: postgres: condition: service_healthy redis: condition: service_healthy labels: - traefik.enable=true - traefik.docker.network=proxy - traefik.http.routers.dify-plugin.rule=Host(`${DIFY_HOST}`) && PathPrefix(`/e/`) - traefik.http.routers.dify-plugin.priority=10 - traefik.http.routers.dify-plugin.entrypoints=websecure - traefik.http.routers.dify-plugin.tls=true - traefik.http.routers.dify-plugin.tls.certresolver=le - traefik.http.routers.dify-plugin.service=dify-plugin - traefik.http.services.dify-plugin.loadbalancer.server.port=5002 dify-sandbox: image: langgenius/dify-sandbox:${DIFY_SANDBOX_VERSION} container_name: dify-sandbox restart: unless-stopped environment: API_KEY: ${DIFY_SANDBOX_API_KEY} GIN_MODE: release WORKER_TIMEOUT: "15" ENABLE_NETWORK: "true" HTTP_PROXY: http://dify-ssrf-proxy:3128 HTTPS_PROXY: http://dify-ssrf-proxy:3128 SANDBOX_PORT: "8194" volumes: - dify_sandbox_deps:/dependencies networks: - internal depends_on: - dify-ssrf-proxy dify-ssrf-proxy: image: ubuntu/squid:latest container_name: dify-ssrf-proxy restart: unless-stopped volumes: - ./dify/ssrf_proxy/squid.conf:/etc/squid/squid.conf:ro networks: - internal # --- Grabcevo (статический лендинг «База Грабцево») ----------------------- # Продающий сайт производственно-складского комплекса. Чистая статика # (index.html + assets/), отдаётся nginx. Домен — кириллический (IDN), # traefik/Let's Encrypt работают по punycode-форме из ${GRABCEVO_HOST}. grabcevo: image: nginx:alpine container_name: grabcevo restart: unless-stopped volumes: - ./grabcevo/site:/usr/share/nginx/html:ro networks: - proxy labels: - traefik.enable=true - traefik.docker.network=proxy - traefik.http.routers.grabcevo.rule=Host(`${GRABCEVO_HOST}`) || Host(`www.${GRABCEVO_HOST}`) - traefik.http.routers.grabcevo.entrypoints=websecure - traefik.http.routers.grabcevo.tls=true - traefik.http.routers.grabcevo.tls.certresolver=le # www → apex (301) - traefik.http.routers.grabcevo.middlewares=grabcevo-wwwredirect - traefik.http.middlewares.grabcevo-wwwredirect.redirectregex.regex=^https?://www\.(.*) - traefik.http.middlewares.grabcevo-wwwredirect.redirectregex.replacement=https://$${1} - traefik.http.middlewares.grabcevo-wwwredirect.redirectregex.permanent=true - traefik.http.services.grabcevo.loadbalancer.server.port=80 dify-weaviate: image: semitechnologies/weaviate:${DIFY_WEAVIATE_VERSION} container_name: dify-weaviate restart: unless-stopped environment: PERSISTENCE_DATA_PATH: /var/lib/weaviate QUERY_DEFAULTS_LIMIT: "25" AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED: "false" DEFAULT_VECTORIZER_MODULE: none CLUSTER_HOSTNAME: node1 AUTHENTICATION_APIKEY_ENABLED: "true" AUTHENTICATION_APIKEY_ALLOWED_KEYS: ${DIFY_WEAVIATE_API_KEY} AUTHENTICATION_APIKEY_USERS: dify@local AUTHORIZATION_ADMINLIST_ENABLED: "true" AUTHORIZATION_ADMINLIST_USERS: dify@local volumes: - dify_weaviate_data:/var/lib/weaviate networks: - internal