Add Dify stack to ru-docker-infra

7 сервисов (api, worker, web, plugin-daemon, sandbox, ssrf-proxy, weaviate)
на общих postgres (базы dify/dify_plugin) и redis (db 2/3). Vector store —
weaviate. Внешний вход через traefik path-роутингом на dify.mxti.ru.
Добавлены: ssrf_proxy/squid.conf, postgres init для баз, DNS-запись,
сервис dify в inventory, deploy-ключ в ssh/.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-24 01:14:17 +03:00
parent 6dd6b586b5
commit f5cbb063a1
7 changed files with 328 additions and 1 deletions
+5
View File
@@ -26,6 +26,11 @@ domains:
server: ru-docker-infra
purpose: "portainer UI"
- host: dify
type: A
server: ru-docker-infra
purpose: "dify через traefik"
- host: n8n
type: A
server: nl-reverse-proxy
+2 -1
View File
@@ -12,7 +12,7 @@ servers:
role: core # основной узел: все ключевые сервисы
env: prod
location: yandex-cloud
tags: [traefik, n8n, gitea, postgres, redis, portainer, xray, mtg]
tags: [traefik, n8n, gitea, postgres, redis, portainer, xray, mtg, dify]
services:
- traefik
- postgres
@@ -23,6 +23,7 @@ servers:
- gitea
- portainer
- mtg # Telegram MTProto-прокси
- dify # LLM-app платформа (api/worker/web/plugin-daemon/sandbox/ssrf-proxy/weaviate)
managed: true
- name: ru-vpn # входная нода VPN
+15
View File
@@ -22,6 +22,21 @@ REDIS_PASSWORD=31hjsbj34rjs
# Сгенерируй: openssl rand -hex 32
N8N_ENCRYPTION_KEY=fbac490fbf63b779fb5f3d90a2fe5445164aaacef1506b02f3f4331c23c2ba59
# --- Dify ---
DIFY_HOST=dify.mxti.ru
# Версии образов (api и web всегда одинаковые)
DIFY_VERSION=1.4.3
DIFY_PLUGIN_DAEMON_VERSION=0.1.2-local
DIFY_SANDBOX_VERSION=0.2.12
# Секреты (сгенерированы: openssl rand -base64 42 / -hex 24)
DIFY_SECRET_KEY=UzWUvFZzVB0Ap0MhhEfQ4r4YiRUBOjJ0O3ra1KUVwwXHoeqmF6OxgqW9
DIFY_PLUGIN_DAEMON_KEY=dAX7IwPuO4ggh1MBASC25G8zfm6DVUZ5pCqKJ5vgQBguYgsSs2OVToDR
DIFY_INNER_API_KEY=7QZ0ijEkf+IAGI/38pQlopq3vhZMGPF682JBS3hSJCyjzGTRuoYZ9A5w
DIFY_WEAVIATE_API_KEY=0fd018d333fae8fb59399a9ddc6051812e06365f64c46740
DIFY_SANDBOX_API_KEY=fd3cd878e0cefe3410a81ecaa30facdd78e1d8357c10b927
# Dify использует общий postgres (базы dify / dify_plugin под POSTGRES_USER)
# и общий redis (db 2 для кэша, db 3 для celery-брокера).
# --- MTProto (mtg) ---
# Фейк-домен для Fake-TLS — ОБЯЗАТЕЛЬНО тот же, под который сгенерирован секрет
# в mtg/config.toml. Traefik матчит TLS-handshake по SNI этого домена (TCP router
@@ -0,0 +1,52 @@
# Dify ssrf_proxy (squid) — отрендеренный конфиг из upstream docker/ssrf_proxy/squid.conf.template.
# Значения подставлены дефолтные: HTTP_PORT=3128, REVERSE_PROXY_PORT=8194,
# SANDBOX_HOST=sandbox, SANDBOX_PORT=8194, COREDUMP_DIR=/var/spool/squid.
# Forward-proxy для api/worker/sandbox (SSRF-защита) + accel reverse-proxy на sandbox.
acl localnet src 0.0.0.1-0.255.255.255
acl localnet src 10.0.0.0/8
acl localnet src 100.64.0.0/10
acl localnet src 169.254.0.0/16
acl localnet src 172.16.0.0/12
acl localnet src 192.168.0.0/16
acl localnet src fc00::/7
acl localnet src fe80::/10
acl SSL_ports port 443
acl Safe_ports port 80
acl Safe_ports port 21
acl Safe_ports port 443
acl Safe_ports port 70
acl Safe_ports port 210
acl Safe_ports port 1025-65535
acl Safe_ports port 280
acl Safe_ports port 488
acl Safe_ports port 591
acl Safe_ports port 777
acl CONNECT method CONNECT
acl allowed_domains dstdomain .marketplace.dify.ai
http_access allow allowed_domains
http_access deny !Safe_ports
http_access deny CONNECT !SSL_ports
http_access allow localhost manager
http_access deny manager
http_access allow localhost
include /etc/squid/conf.d/*.conf
http_access deny all
http_port 3128
coredump_dir /var/spool/squid
refresh_pattern ^ftp: 1440 20% 10080
refresh_pattern ^gopher: 1440 0% 1440
refresh_pattern -i (/cgi-bin/|\?) 0 0% 0
refresh_pattern \/(Packages|Sources)(|\.bz2|\.gz|\.xz)$ 0 0% 0 refresh-ims
refresh_pattern \/Release(|\.gpg)$ 0 0% 0 refresh-ims
refresh_pattern \/InRelease$ 0 0% 0 refresh-ims
refresh_pattern \/(Translation-.*)(|\.bz2|\.gz|\.xz)$ 0 0% 0 refresh-ims
refresh_pattern . 0 20% 4320
http_port 8194 accel vhost
cache_peer sandbox parent 8194 0 no-query originserver
acl src_all src all
http_access allow src_all
client_request_buffer_max_size 100 MB
+219
View File
@@ -11,6 +11,10 @@ volumes:
redis_data:
portainer_data:
gitea_data:
dify_api_storage:
dify_plugin_storage:
dify_weaviate_data:
dify_sandbox_deps:
services:
traefik:
@@ -50,6 +54,7 @@ services:
POSTGRES_DB: ${POSTGRES_DB}
volumes:
- postgres_data:/var/lib/postgresql/data
- ./postgres/init:/docker-entrypoint-initdb.d:ro
networks:
- internal
healthcheck:
@@ -228,3 +233,217 @@ services:
- traefik.tcp.routers.mtg.entrypoints=websecure
- traefik.tcp.routers.mtg.tls.passthrough=true
- traefik.tcp.services.mtg.loadbalancer.server.port=3128
# --- 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}
# --- 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
# --- 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`))
- 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:
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
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
dify-weaviate:
image: semitechnologies/weaviate:1.19.0
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
@@ -0,0 +1,10 @@
-- Создание баз для Dify в общем postgres.
-- ВАЖНО: скрипты из /docker-entrypoint-initdb.d/ выполняются ТОЛЬКО при первой
-- инициализации (пустой data-каталог). На уже работающем postgres базы нужно
-- создать вручную один раз (см. servers/ru-docker-infra/README или CLAUDE.md):
-- docker exec n8n-postgres psql -U n8n -c "CREATE DATABASE dify;"
-- docker exec n8n-postgres psql -U n8n -c "CREATE DATABASE dify_plugin;"
-- Dify подключается под существующим суперпользователем postgres (POSTGRES_USER).
SELECT 'CREATE DATABASE dify' WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'dify')\gexec
SELECT 'CREATE DATABASE dify_plugin' WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'dify_plugin')\gexec
+25
View File
@@ -0,0 +1,25 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAqCl4Bg7SRNO/t28BlAcuO+wJaiBYRfN+fGg3h1ykuVujNKteXwnVVZze
hIc6aPOXRvDa0ssHNARSlE0SCQ7Q6rGXnoKlq7z791r6YZiJ7PQ5oCKA+J53WdB8abtO59i2
15I1vUQffmBfuzkA3MQxhC8NJOoOVBzk7ICbEKsJGMePeieyI/iL3wi6Bwukulix9shqRysZ
i1hTN31y5Do83pIUoTnE2sk8O2mRSqpyWPyWEmvCSNZrmPxYlqUCwj61OMZhAp8Y1wVW0arj
Es3p8EY7494QYIZbENS9iWIUflmne0ms5ZHGmD8XJ83bucxnOaYu7llzrXvB46DHIHd3HwID
AQABAoIBABBE5tBLOcuf4uH30uLD6RSyCt0iIPgvjRKzMIAfle467+jGFZzDQkkEosCWpq+F
taKO5dTUAK/nJSgk5rRKoyq5oM4cnLOnVodBXYz4qYtru3GDNRAYoLpnQcom7YTfwhzMD/fu
Mc/Tuh9Obgdt0c6UkMR8AoPfwcQh0x7DNV+Ysoj9lguvUF6Y6owajoYWxOz0PlpQ6PG6QNvW
A39LV+O6mIas/Wc0uA6R4ztbsgmzxm0HVhVfesb67x68Q9iGSrrkZNadOFcMXIRJ1aofuXlX
Kbltt+tiegZ9P6Mn04F20b6WFi2arZaPl+ZWsiBAZZmsxTA9EteU1DwgXKfYgQ0CgYEA7MJm
yyhTIOwmdbhz2ITwMmOU2qPtGnKIUbFZipPTRPjzdxClc79KU3DeeAnIg+zgKM9vvWwwq6WJ
wYhMMtryaoWVPW7EfHlxAciFXze1KlJIwtsKX6WP47LFWmUPwp5+X6TUgVIjsjrgWLtMS5Ae
kmoVnOvT5UnR6k800jUqrL0CgYEAtdPzaQ+9E1Z6dLdc4ofDGZ5B5vJthygGq7PZryFPkDr0
GZtrZKlPLM/gCUZqui5DdvCB1q8pfRIf1jI+yXOyohCYaOwC3eyfB22ga2kFQ3Cpys+jNKcV
A41UHwhSnbpgBsGmqOSw9B2HgR3Z3ZCDn/TgdWIj4oul+TbZD0vKZwsCgYEA1pmC6QRdDQtf
9PZC8u/UdCf1BZTuVjmYPEv/eG9zYSIyNCr/fQHXkfhNfgqIGKQ9rmAPzWdiWkMKsUQkpTf1
4P3B/1QGfzjoaJBj66Gn8Xtx/kTiiz2bRe2FmuDdvy9AJttVzv4v4t4g+eyTRC6Wt2m2u2y6
vSsDPVmb7ClKzBUCgYEAnHEfNd7PvEqkk7WMGm/awjjy1MTyzd33V1YnzlGnxh2jFkEttm8h
6xlz1ica/tN9QmGOJOuSk8S7Vaznbqa4JE6JA4VEtjK3ZUnCQ4gZejv+KrxZ3JyBLQw1O3oK
syC/LDEDtr8FLRQXGVVYtRR423ByiyyEWY/znqe9PDbmj58CgYB/wOFyktT3HcV89ZVCLGQp
HfQ6yreocCDGiUucIvfwGH8cUjclfffi6TBA1MqFTSRWjcE+tq/LjEdHWO7yG4s8Gx072U66
KjPCoUb1QsZ/o2dJEG2yOVQbviP3KrfdZfp76SYCVx4qz0jS9vIMusBvdad6uLunMMbbBdew
+x4Bvg==
-----END RSA PRIVATE KEY-----