# DataSteward · 单机全栈 compose(多架构:amd64/arm64) # # 快速开始:cp .env.example .env(填 LLM 配置)→ bash deploy/quickstart.sh # 手动等价步骤见 infra/README.md。内存参考(默认 profile ≈9GB,+app ≈12GB):16GB 机器可跑。 # # 绑定策略:数据库端口只绑 127.0.0.1,UI/服务端口绑 0.0.0.0(局域网可看)。 # 所有宿主端口都可用 .env 里的 DM_PORT_* 重映射(与已有服务冲突时改 .env 即可)。 # # profiles: # (默认) 数据基建:starrocks / postgres / flink×2 / neo4j # app 应用层:dm-app(Streamlit) / dagster-webserver / dagster-daemon # tools dm-cli(docker compose run --rm dm-cli <命令> 跑一次性任务) # ollama 本地 LLM(可选;零云依赖,需 ~6GB 额外内存或 GPU;.env 里三行切换) # dingtalk 钉钉 Stream 双向通道(可选;需 DINGTALK_APP_KEY/SECRET + role_map.yaml) # sim-u8 用友 U8 仿真源(可选;x86 可用,aarch64 已知不工作——DEVLOG 坑22) # om OpenMetadata 三件套(可选重型件,额外 ~6GB 内存) name: datasteward x-dm-app-common: &dm-app-common image: datasteward/dm:latest build: context: .. dockerfile: deploy/Dockerfile env_file: - path: ../.env required: false environment: &dm-app-env DM_DATA_DIR: /data DM_WH_HOST: starrocks DM_WH_PORT: "9030" DM_SRC_PG_HOST: postgres DM_SRC_PG_PORT: "5432" DM_FLINK_REST: http://flink-jobmanager:8081 DM_NEO4J_URI: bolt://neo4j:7687 # LLM:任意 OpenAI 兼容端点。默认取 .env(云 API 或本地 ollama 皆可); # 未配置时回落到 compose 内 ollama 服务(--profile ollama 启用后即通)。 # 注意:若走宿主机自装的 ollama,用 http://host.docker.internal:11434/v1(host-gateway 已配)。 # 经验(DEVLOG 坑29):本地推理务必直连 ollama /v1,不要过 LiteLLM 一类网关—— # 网关对客户端断开不取消上游推理("取消黑洞"),长生成会占死并发槽。 DM_LLM_BASE_URL: ${DM_LLM_BASE_URL:-http://ollama:11434/v1} DM_LLM_API_KEY: ${DM_LLM_API_KEY:-sk-local} DM_LLM_MODEL: ${DM_LLM_MODEL:-qwen3:8b} # 合成数据环境的"今天"(generate.py TODAY / dbt anchor_today 同值);接真库时删除此行回归墙钟 DM_ANCHOR_TODAY: "2026-06-25" DAGSTER_PG_URL: postgresql://${DM_SRC_PG_USER:-dm}:${DM_SRC_PG_PASSWORD:-dm_dev_pass}@postgres:5432/dagster DAGSTER_HOME: /dagster_home DM_DBT_DIR: /app/transform/dbt PYTHONUTF8: "1" HF_HUB_OFFLINE: "1" DM_EMBED_CACHE: /data/embed_cache volumes: - ../data:/data - dagster_home:/dagster_home extra_hosts: - "host.docker.internal:host-gateway" restart: unless-stopped services: starrocks: image: starrocks/allin1-ubuntu:latest # 关键(DEVLOG 坑1):allin1 默认把 FE/BE 的 priority_networks 设成 127.0.0.1,导致跨容器 # Stream Load 重定向到 127.0.0.1:8040 不可达。entrypoint 预设为容器 IP。 entrypoint: - /bin/bash - -c - | IP=$$(grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' /etc/hosts | grep -v '^127\.' | awk '{print $$1; exit}') [ -z "$$IP" ] && IP=$$(hostname -I 2>/dev/null | awk '{print $$1}') echo "dm: using container IP $$IP for FE/BE priority_networks + director MYHOST" grep -q '^priority_networks' /data/deploy/starrocks/fe/conf/fe.conf || echo "priority_networks = $$IP/32" >> /data/deploy/starrocks/fe/conf/fe.conf grep -q '^priority_networks' /data/deploy/starrocks/be/conf/be.conf || echo "priority_networks = $$IP/32" >> /data/deploy/starrocks/be/conf/be.conf sed -i "s|^MYHOST=127.0.0.1|MYHOST=$$IP|" /data/deploy/starrocks/director/run.sh exec /data/deploy/entrypoint.sh ports: - "127.0.0.1:${DM_PORT_SR:-9030}:9030" # FE MySQL 协议 - "127.0.0.1:${DM_PORT_SR_HTTP:-8030}:8030" # FE HTTP / Stream Load - "127.0.0.1:${DM_PORT_SR_BE:-8040}:8040" # BE HTTP volumes: - sr_data:/data/deploy/starrocks mem_limit: 4g healthcheck: test: ["CMD-SHELL", "mysql -h127.0.0.1 -P9030 -uroot -e 'show frontends' >/dev/null 2>&1 || exit 1"] interval: 15s timeout: 10s retries: 40 start_period: 120s restart: unless-stopped postgres: image: pgvector/pgvector:pg16 command: - "postgres" - "-c" - "wal_level=logical" # Flink CDC(Debezium/pgoutput)必需 - "-c" - "max_wal_senders=30" - "-c" - "max_replication_slots=30" environment: POSTGRES_USER: ${DM_SRC_PG_USER:-dm} POSTGRES_PASSWORD: ${DM_SRC_PG_PASSWORD:-dm_dev_pass} POSTGRES_DB: ${DM_SRC_PG_DB:-dm} ports: - "127.0.0.1:${DM_PORT_PG:-15432}:5432" volumes: - pg_data:/var/lib/postgresql/data mem_limit: 512m healthcheck: test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"] interval: 10s timeout: 5s retries: 20 restart: unless-stopped flink-jobmanager: image: flink:1.18-scala_2.12-java11 command: jobmanager ports: - "0.0.0.0:${DM_PORT_FLINK:-8081}:8081" # Flink Web UI environment: - | FLINK_PROPERTIES= jobmanager.rpc.address: flink-jobmanager jobmanager.memory.process.size: 1024m parallelism.default: 1 execution.checkpointing.interval: 5s volumes: - ../infra/flink-lib/flink-sql-connector-postgres-cdc-3.0.1.jar:/opt/flink/lib/flink-sql-connector-postgres-cdc-3.0.1.jar:ro - ../infra/flink-lib/flink-connector-starrocks-1.2.9_flink-1.18.jar:/opt/flink/lib/flink-connector-starrocks-1.2.9_flink-1.18.jar:ro mem_limit: 1200m restart: unless-stopped flink-taskmanager: image: flink:1.18-scala_2.12-java11 command: taskmanager depends_on: - flink-jobmanager environment: # 19 表 CDC 是单作业 parallelism=1,2 个槽已够(笔记本档;大机器可调大) - | FLINK_PROPERTIES= jobmanager.rpc.address: flink-jobmanager taskmanager.numberOfTaskSlots: 2 taskmanager.memory.process.size: 1728m parallelism.default: 1 volumes: - ../infra/flink-lib/flink-sql-connector-postgres-cdc-3.0.1.jar:/opt/flink/lib/flink-sql-connector-postgres-cdc-3.0.1.jar:ro - ../infra/flink-lib/flink-connector-starrocks-1.2.9_flink-1.18.jar:/opt/flink/lib/flink-connector-starrocks-1.2.9_flink-1.18.jar:ro mem_limit: 2g restart: unless-stopped neo4j: image: neo4j:5-community environment: NEO4J_AUTH: neo4j/${DM_NEO4J_PASSWORD:-datasteward-dev} # 笔记本档内存(大机器可上调 heap/pagecache) NEO4J_server_memory_heap_initial__size: 512m NEO4J_server_memory_heap_max__size: 512m NEO4J_server_memory_pagecache_size: 256m ports: - "127.0.0.1:${DM_PORT_NEO4J_HTTP:-7474}:7474" - "127.0.0.1:${DM_PORT_NEO4J_BOLT:-7687}:7687" volumes: - neo4j_data:/data mem_limit: 1500m healthcheck: test: ["CMD-SHELL", "wget -qO- http://localhost:7474 >/dev/null 2>&1 || exit 1"] interval: 15s timeout: 10s retries: 20 restart: unless-stopped # ---- 本地 LLM(可选,--profile ollama):拉起后在 .env 里把 LLM 三行指向它 ---- ollama: profiles: ["ollama"] image: ollama/ollama:latest ports: - "127.0.0.1:${DM_PORT_OLLAMA:-11434}:11434" volumes: - ollama_models:/root/.ollama restart: unless-stopped # 一次性:自动拉默认模型(qwen3:8b,~5GB;改 DM_OLLAMA_PULL 换模型) ollama-init: profiles: ["ollama"] image: ollama/ollama:latest depends_on: - ollama environment: OLLAMA_HOST: http://ollama:11434 entrypoint: ["/bin/sh", "-c"] command: ["ollama pull ${DM_OLLAMA_PULL:-qwen3:8b}"] restart: "no" # ---- 仿真 U8 源(可选,--profile sim-u8):azure-sql-edge 在 x86 正常; # aarch64 新内核上 sqlservr 空转不监听(DEVLOG 坑22),aarch64 请用外部 MSSQL ---- sim-u8: profiles: ["sim-u8"] image: mcr.microsoft.com/azure-sql-edge:latest environment: ACCEPT_EULA: "1" MSSQL_SA_PASSWORD: ${MSSQL_SA_PASSWORD:-SimU8!Passw0rd} ports: - "127.0.0.1:${DM_PORT_MSSQL:-11433}:1433" volumes: - u8_data:/var/opt/mssql mem_limit: 2g healthcheck: # 必须显式 bash:CMD-SHELL 走 /bin/sh(dash),不支持 /dev/tcp test: ["CMD", "bash", "-c", "exec 3<>/dev/tcp/127.0.0.1/1433"] interval: 10s timeout: 5s retries: 30 start_period: 30s restart: unless-stopped # ================= 应用层(profile: app)================= dm-app: <<: *dm-app-common profiles: ["app"] command: ["dm-app", "--server.port", "8501", "--server.address", "0.0.0.0", "--server.headless", "true"] ports: - "0.0.0.0:${DM_PORT_APP:-8501}:8501" depends_on: starrocks: condition: service_healthy postgres: condition: service_healthy mem_limit: 1g healthcheck: test: ["CMD-SHELL", "python -c \"import urllib.request;urllib.request.urlopen('http://127.0.0.1:8501/_stcore/health',timeout=5)\" || exit 1"] interval: 20s timeout: 10s retries: 10 start_period: 30s # 钉钉 Stream 双向(可选,--profile dingtalk):需 .env 里 DINGTALK_APP_KEY/SECRET, # 并先 cp deploy/role_map.yaml.template role_map.yaml(否则 docker 会把缺失 bind 源建成目录) dm-dingtalk: <<: *dm-app-common profiles: ["dingtalk"] command: ["dm-dingtalk", "stream"] volumes: # 覆盖锚点 volumes(compose 合并对列表是整体替换),须把公共两条一并带上 - ../data:/data - dagster_home:/dagster_home - ../role_map.yaml:/app/role_map.yaml:ro # 角色映射热加载 depends_on: starrocks: condition: service_healthy mem_limit: 512m # dagster.yaml 必须落在 $DAGSTER_HOME 里才生效(否则退回 sqlite,双进程并发出 alembic 多头) dagster-webserver: <<: *dm-app-common profiles: ["app"] command: ["bash", "-c", "cp /app/deploy/dagster/dagster.yaml $$DAGSTER_HOME/ && exec dagster-webserver -h 0.0.0.0 -p 3070 -w /app/deploy/dagster/workspace.yaml"] ports: - "0.0.0.0:${DM_PORT_DAGSTER:-3070}:3070" depends_on: postgres: condition: service_healthy mem_limit: 768m dagster-daemon: <<: *dm-app-common profiles: ["app"] command: ["bash", "-c", "cp /app/deploy/dagster/dagster.yaml $$DAGSTER_HOME/ && exec dagster-daemon run -w /app/deploy/dagster/workspace.yaml"] depends_on: postgres: condition: service_healthy mem_limit: 768m # 一次性任务跑批入口:docker compose run --rm dm-cli dm-load / dm-eval / dm-u8 full ... dm-cli: <<: *dm-app-common profiles: ["tools"] restart: "no" command: ["bash"] # ================= OpenMetadata(profile: om;可选重型件)================= om-mysql: image: mysql:8.3 profiles: ["om"] environment: MYSQL_ROOT_PASSWORD: ${OM_MYSQL_ROOT_PASSWORD:-om_dev_root} MYSQL_USER: openmetadata_user MYSQL_PASSWORD: ${OM_MYSQL_PASSWORD:-openmetadata_password} MYSQL_DATABASE: openmetadata_db command: ["--sort_buffer_size=10M"] volumes: - om_mysql:/var/lib/mysql healthcheck: test: ["CMD-SHELL", "mysqladmin ping -h localhost -u root -p$${MYSQL_ROOT_PASSWORD} --silent"] interval: 15s timeout: 10s retries: 20 restart: unless-stopped om-opensearch: image: opensearchproject/opensearch:2.19.0 profiles: ["om"] environment: discovery.type: single-node DISABLE_SECURITY_PLUGIN: "true" OPENSEARCH_JAVA_OPTS: "-Xms1g -Xmx1g" volumes: - om_opensearch:/usr/share/opensearch/data healthcheck: test: ["CMD-SHELL", "curl -s http://localhost:9200/_cluster/health >/dev/null || exit 1"] interval: 15s timeout: 10s retries: 20 restart: unless-stopped om-migrate: image: openmetadata/server:1.5.11 profiles: ["om"] command: ["./bootstrap/openmetadata-ops.sh", "migrate"] environment: &om-env DB_DRIVER_CLASS: com.mysql.cj.jdbc.Driver DB_SCHEME: mysql DB_PARAMS: allowPublicKeyRetrieval=true&useSSL=false&serverTimezone=UTC DB_USER: openmetadata_user DB_USER_PASSWORD: ${OM_MYSQL_PASSWORD:-openmetadata_password} DB_HOST: om-mysql DB_PORT: "3306" OM_DATABASE: openmetadata_db SEARCH_TYPE: opensearch ELASTICSEARCH_HOST: om-opensearch ELASTICSEARCH_PORT: "9200" ELASTICSEARCH_SCHEME: http depends_on: om-mysql: condition: service_healthy om-opensearch: condition: service_healthy restart: "no" openmetadata-server: image: openmetadata/server:1.5.11 profiles: ["om"] environment: <<: *om-env OPENMETADATA_CLUSTER_NAME: datasteward SERVER_PORT: "8585" ports: - "0.0.0.0:${DM_PORT_OM:-8585}:8585" depends_on: om-migrate: condition: service_completed_successfully healthcheck: test: ["CMD-SHELL", "curl -s http://localhost:8586/healthcheck >/dev/null || exit 1"] interval: 20s timeout: 10s retries: 30 start_period: 60s restart: unless-stopped volumes: sr_data: pg_data: neo4j_data: u8_data: dagster_home: ollama_models: om_mysql: om_opensearch: