How to Add Custom Services Using the Dream Server Extension System

The Dream Server extension system enables custom services through a declarative, manifest-driven architecture that automatically registers services from extensions/services/ directories and validates their Docker Compose fragments without modifying core installer code.

The Light-Heart-Labs/DreamServer project provides a modular runtime for AI services where adding custom functionality requires only a YAML manifest and an optional Compose file. By treating every service as a self-contained extension, the system separates service definitions from the core orchestration logic, allowing users to integrate new AI agents, APIs, or databases through a secure, reproducible pipeline.

Understanding the Extension System Architecture

Dream Server processes extensions through two complementary phases that run automatically when the CLI starts. This design ensures that services are discovered, validated, and integrated into the Docker Compose stack without manual configuration edits.

Phase 1: Service Discovery and Registration

The registration process begins in dream-server/lib/service-registry.sh, which walks the extensions/services/ tree and the optional data/user-extensions directory. For each subdirectory, the sr_load() function (lines 54-108) searches for manifest files (manifest.yaml, manifest.yml, or manifest.json).

The loader uses embedded Python with PyYAML to parse each manifest and validate required fields including schema_version, service.id, and service.container_name. Upon successful validation, the Python block emits Bash associative-array assignments that populate the global registry (lines 26-45). These arrays store:

  • SERVICE_IDS – The list of registered service identifiers
  • SERVICE_ALIASES – Short names mapping to canonical IDs
  • SERVICE_COMPOSE – Paths to Docker Compose fragments
  • SERVICE_GPU_BACKENDS – Supported GPU targets (AMD, NVIDIA, Apple, CPU)

GPU-backend filters are applied during this phase, ensuring a service only registers if it supports the current hardware configuration.

Phase 2: Docker Compose Resolution and Security Validation

After registration, dream-server/scripts/resolve-compose-stack.sh executes (lines 50-124 and 150-210) to build the final docker compose command. This script rescans the extension directories, extracts the compose_file entry from each manifest, and validates security constraints through the _scan_user_compose_content() function (lines 111-246).

The security scanner rejects configurations that include:

  • Privileged mode (privileged: true)
  • Host network mode (network_mode: host)
  • Dangerous capabilities or bind-mounts of system files like /etc/passwd
  • Port bindings outside 127.0.0.1

The script also checks config/core-service-ids.json (referenced in lines 176-187) to prevent user extensions from declaring IDs that collide with built-in core services.

Step-by-Step Guide to Adding a Custom Service

Adding a new service requires no changes to the Dream Server core. You only need to create a directory with a manifest and a Compose fragment.

Step 1: Create the Service Directory Structure

Create a directory under extensions/services/ using lowercase names with hyphens:

dream-server/extensions/services/my-awesome-bot/

Step 2: Define the Service Manifest

Create a manifest.yaml file that declares the service metadata. The manifest must conform to schema_version: dream.services.v1:

schema_version: dream.services.v1
service:
  id: awesome-bot
  name: Awesome Bot (Chat Agent)
  aliases: [bot, chat-bot]
  container_name: dream-awesome-bot
  port: 5005
  external_port_env: BOT_PORT
  health: /health
  gpu_backends: [none]
  category: optional
  depends_on: []
  compose_file: compose.yaml

The gpu_backends field accepts an array such as ["amd", "nvidia"] to indicate hardware support. For a complete reference, examine the built-in Llama-Server manifest at dream-server/extensions/services/llama-server/manifest.yaml.

Step 3: Create the Docker Compose Fragment

Add a compose.yaml file that defines the container configuration. This fragment must obey the security constraints enforced by _scan_user_compose_content():

version: "3.9"
services:
  awesome-bot:
    image: ghcr.io/yourorg/awesome-bot:latest
    ports:
      - "127.0.0.1:${BOT_PORT:-5005}:5005"
    environment:
      - SOME_SETTING=prod

Key requirements include binding ports to localhost (127.0.0.1), avoiding privileged: true, and refraining from absolute host mounts.

Step 4: Configure GPU Overlays (Optional)

If your service supports GPU acceleration, place hardware-specific overlays beside the base file:

The resolver automatically selects the correct overlay based on the detected backend.

Step 5: Register and Start the Service

Reload the registry to parse the new manifest. In a running shell, source the library and invoke sr_load():

. "$PWD/dream-server/lib/service-registry.sh"
sr_load

Start the service using the CLI:

dream start awesome-bot

Internally, the dream command executes docker compose $(sr_compose_flags) up -d awesome-bot, where sr_compose_flags() (lines 300-324) generates the list of -f flags pointing to your new compose.yaml.

Understanding the Security Model

The _scan_user_compose_content() function implements a defense-in-depth strategy for user-contributed extensions. Before any container starts, the resolver validates that Compose fragments do not:

  • Escalate privileges or access host namespaces
  • Expose ports on public interfaces (non-loopback bindings are rejected)
  • Mount sensitive host paths

This validation ensures that custom services run within the same security boundary as built-in services, preventing container escape or lateral movement even if a third-party extension is compromised.

Key Implementation Files

The following source files contain the core logic for the extension system:

Summary

  • The Dream Server extension system treats services as self-contained plugins defined by YAML manifests.
  • Service registration occurs via sr_load() in dream-server/lib/service-registry.sh, which populates Bash associative arrays for service metadata.
  • Security validation happens in dream-server/scripts/resolve-compose-stack.sh, which scans for privileged modes, dangerous mounts, and network configurations.
  • Custom services require only a directory under extensions/services/ containing a manifest.yaml and a Compose fragment.
  • GPU support is handled through optional overlay files (compose.nvidia.yaml, compose.amd.yaml) selected automatically by the resolver.
  • The dream CLI commands (start, stop, restart, update) integrate custom services automatically through the sr_compose_flags() function.

Frequently Asked Questions

What file format should I use for the service manifest?

Dream Server accepts YAML (.yaml or .yml) or JSON (.json) manifest files. YAML is strongly recommended for readability and is the format used by all built-in services. The file must declare schema_version: dream.services.v1 and include required fields such as service.id, service.container_name, and compose_file.

Can I override built-in Dream Server services with custom extensions?

No. The extension system prevents ID collisions by checking config/core-service-ids.json during the resolution phase. If a user extension declares a service.id that matches any core service (such as llama-server or web-ui), the registration will fail. This safeguard ensures that critical infrastructure services cannot be accidentally replaced or shadowed.

How does Dream Server handle GPU-specific configurations for custom services?

The manifest includes a gpu_backends array indicating which hardware platforms the service supports (e.g., ["amd", "nvidia"] or ["none"] for CPU-only). During stack resolution, the script selects the appropriate Docker Compose overlay file (such as compose.nvidia.yaml or compose.amd.yaml) based on the currently detected backend. If no GPU overlay exists, the base compose.yaml is used.

What security restrictions apply to custom service Docker Compose files?

All user-provided Compose fragments are validated by _scan_user_compose_content() in dream-server/scripts/resolve-compose-stack.sh. Forbidden configurations include privileged: true, network_mode: host, dangerous capability additions, and host bind-mounts of sensitive paths like /etc/passwd. Additionally, port bindings must use 127.0.0.1 to prevent external exposure. These constraints ensure that custom extensions cannot compromise the host system or escape the container sandbox.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →