LangBot Docker & Self-Hosted Deployment

GitHub

Production-grade platform for building agentic IM bots - 生产级多平台智能机器人开发平台/ Agent、知识库编排、插件系统 / Bots for Discord / Slack / LINE / Telegram / WeChat(企业微信,...

17,347 stars Python Python 1 file · ~12,062 tokens #agent#coze#deepseek#dify#dingtalk#discord#feishu#kook
RAW Compose
1. Pull Image (DOCKERHUB) docker pull rockchin/langbot
2. Docker Run (1-Click Launch) docker run -d --name langbot -p 5401:5401 --restart always rockchin/langbot:latest
3. Podman Run podman run -d --name langbot -p 5401:5401 --restart always rockchin/langbot:latest
4. Skopeo Inspect (Zero-Pull Manifest) skopeo inspect docker://docker.io/rockchin/langbot:latest
5. ORAS Pull (OCI Artifact) oras pull rockchin/langbot:latest
6. Compose Deployment docker compose up -d
docker/docker-compose.yaml
Download .yml
# Docker Compose configuration for LangBot
# For Kubernetes deployment, see kubernetes.yaml and the deployment guide at https://langbot.app/docs
version: "3"

services:

  langbot_plugin_runtime:
    image: rockchin/langbot:latest
    container_name: langbot_plugin_runtime
    volumes:
      - ./data/plugins:/app/data/plugins
    ports:
      - 5401:5401
    restart: on-failure
    environment:
      - TZ=Asia/Shanghai
      # Optional. Leave unset on both OSS services, or set the same value on
      # both to protect the control WebSocket. Generate with: openssl rand -hex 32
      - LANGBOT_PLUGIN_RUNTIME_CONTROL_TOKEN=${LANGBOT_PLUGIN_RUNTIME_CONTROL_TOKEN:-}
      # Process-wide admission for every asyncio.to_thread() call.
      - LANGBOT_BLOCKING_EXECUTOR_MAX_WORKERS=${LANGBOT_BLOCKING_EXECUTOR_MAX_WORKERS:-8}
      - LANGBOT_BLOCKING_EXECUTOR_MAX_PENDING=${LANGBOT_BLOCKING_EXECUTOR_MAX_PENDING:-128}
      - LANGBOT_BLOCKING_EXECUTOR_MAX_INFLIGHT_PER_SCOPE=${LANGBOT_BLOCKING_EXECUTOR_MAX_INFLIGHT_PER_SCOPE:-4}
    command: ["uv", "run", "--no-sync", "-m", "langbot_plugin.cli.__init__", "rt"]
    # The control WebSocket port also serves an unauthenticated GET /healthz
    # (see langbot_plugin runtime/io/controllers/ws/server.py). curl is removed
    # from the image, so probe with the always-present python.
    healthcheck:
      test: ["CMD", "python", "-c", "import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://127.0.0.1:5400/healthz', timeout=3).getcode()==200 else 1)"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 30s
    networks:
      - langbot_network

  # The Box sandbox runtime is optional. It is only started when you run
  # ``docker compose --profile box up`` (or ``docker compose --profile all
  # up``). With Box off, LangBot keeps skill management, activation, and
  # read-only resources available but disables execution tools and stdio MCP —
  # set ``box.enabled: false`` in ``data/config.yaml`` (or
  # ``BOX__ENABLED=false`` in the langbot service env below) to match.
  langbot_box:
    image: rockchin/langbot:latest
    container_name: langbot_box
    profiles: ["box", "all"]
    volumes:
      # Keep the source and target path identical because langbot_box uses the
      # host Docker socket to create sandbox containers. Override
      # LANGBOT_BOX_ROOT with an absolute path if you do not want the default.
      - ${LANGBOT_BOX_ROOT:-${PWD}/data/box}:${LANGBOT_BOX_ROOT:-${PWD}/data/box}
      # Mount container runtime socket for Box sandbox backend.
      # Uncomment the one that matches your container runtime:
      # - /var/run/podman/podman.sock:/var/run/podman/podman.sock   # Podman
      - /var/run/docker.sock:/var/run/docker.sock                   # Docker
    restart: on-failure
    environment:
      - TZ=Asia/Shanghai
      # Optional shared control-plane secret used to authenticate both the RPC
      # socket and managed-process relay. Leave unset on both OSS services, or
      # generate one with ``openssl rand -hex 32`` and set the same value on
      # both ends. Strongly recommended when the deployment is Internet-accessible.
      - LANGBOT_BOX_CONTROL_TOKEN=${LANGBOT_BOX_CONTROL_TOKEN:-}
      # Box has its own process-wide blocking-work budget.
      - LANGBOT_BLOCKING_EXECUTOR_MAX_WORKERS=${LANGBOT_BLOCKING_EXECUTOR_MAX_WORKERS:-8}
      - LANGBOT_BLOCKING_EXECUTOR_MAX_PENDING=${LANGBOT_BLOCKING_EXECUTOR_MAX_PENDING:-128}
      - LANGBOT_BLOCKING_EXECUTOR_MAX_INFLIGHT_PER_SCOPE=${LANGBOT_BLOCKING_EXECUTOR_MAX_INFLIGHT_PER_SCOPE:-4}
      # The Box runtime does NOT read box.local.* from config.yaml or env; it
      # receives its functional configuration from LangBot via the INIT RPC
      # action. Do not add BOX__* here because those would be ignored.
    # Launched through the same CLI entry point as the plugin runtime
    # (`langbot_plugin.cli.__init__ <subcommand>`). WebSocket is the default
    # control transport — mirrors `rt`, which also runs with no flag. Pass
    # `-s` / `--stdio-control` only for the stdio mode LangBot uses outside
    # containers.
    command: ["uv", "run", "--no-sync", "-m", "langbot_plugin.cli.__init__", "box"]
    # Box serves GET /healthz (liveness) and GET /readyz (readiness, 503 until
    # the configured backend and isolation guarantees are ready) on the control
    # port (default 5410). Use /readyz so the container is only reported healthy
    # once it can actually accept sandbox work.
    healthcheck:
      test: ["CMD", "python", "-c", "import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://127.0.0.1:5410/readyz', timeout=3).getcode()==200 else 1)"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 20s
    networks:
      - langbot_network

  langbot:
    image: rockchin/langbot:latest
    container_name: langbot
    volumes:
      - ./data:/app/data
      # Core owns the SkillRepository even when the Box profile is disabled.
      # Keep this path identical to langbot_box so optional execution can
      # consume the same Workspace-scoped package revisions.
      - ${LANGBOT_BOX_ROOT:-${PWD}/data/box}:${LANGBOT_BOX_ROOT:-${PWD}/data/box}
    restart: on-failure
    environment:
      - TZ=Asia/Shanghai
      # Optional. Leave unset on both OSS services, or match plugin Runtime.
      - LANGBOT_PLUGIN_RUNTIME_CONTROL_TOKEN=${LANGBOT_PLUGIN_RUNTIME_CONTROL_TOKEN:-}
      # When set, this must match langbot_box. If both ends leave it unset,
      # OSS permits the connection without token authentication. The token is
      # sent only in WebSocket handshake headers, never in URLs or payloads.
      - LANGBOT_BOX_CONTROL_TOKEN=${LANGBOT_BOX_CONTROL_TOKEN:-}
      # Core process-wide blocking-work admission. These are native config
      # overrides and are persisted with the effective data/config.yaml.
      - SYSTEM__BLOCKING_EXECUTOR__MAX_WORKERS=${LANGBOT_BLOCKING_EXECUTOR_MAX_WORKERS:-8}
      - SYSTEM__BLOCKING_EXECUTOR__MAX_PENDING=${LANGBOT_BLOCKING_EXECUTOR_MAX_PENDING:-128}
      - SYSTEM__BLOCKING_EXECUTOR__MAX_INFLIGHT_PER_SCOPE=${LANGBOT_BLOCKING_EXECUTOR_MAX_INFLIGHT_PER_SCOPE:-4}
      # Unified env-override convention: SECTION__SUBSECTION__KEY overrides the
      # matching config.yaml field (see LoadConfigStage). These map onto
      # box.* and are forwarded to the Box runtime via INIT RPC.
      - BOX__LOCAL__HOST_ROOT=${LANGBOT_BOX_ROOT:-${PWD}/data/box}
      - BOX__LOCAL__DEFAULT_WORKSPACE=default
      # TODO(next-major): default LANGBOT_SKILLS_ROOT to ${PWD}/data/skills
      # after the historical Box storage path no longer needs zero-copy upgrades.
      - SKILLS__ROOT=${LANGBOT_SKILLS_ROOT:-${LANGBOT_BOX_ROOT:-${PWD}/data/box}/skills}
      - BOX__LOCAL__ALLOWED_MOUNT_ROOTS=${LANGBOT_BOX_ROOT:-${PWD}/data/box}
      - BOX__DOCKER__CPU_LIMIT_ENABLED=${LANGBOT_BOX_DOCKER_CPU_LIMIT_ENABLED:-true}
    ports:
      - 5300:5300  # For web ui and webhook callback
      - 2280-2285:2280-2285  # For platform reverse connection
    # The core Quart app exposes an unauthenticated GET /healthz on api.port
    # (see langbot/pkg/api/http/controller/main.py). curl is removed from the
    # image, so probe with the always-present python.
    healthcheck:
      test: ["CMD", "python", "-c", "import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://127.0.0.1:5300/healthz', timeout=3).getcode()==200 else 1)"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 60s
    networks:
      - langbot_network

networks:
  langbot_network:
    driver: bridge