# How to Add Custom Services Using the Dream Server Extension System

> Learn how the Dream Server extension system adds custom services using a declarative architecture. Auto-register services and validate Docker Compose without core code changes.

- Repository: [Light Heart Labs/DreamServer](https://github.com/Light-Heart-Labs/DreamServer)
- Tags: how-to-guide
- Published: 2026-05-18

---

**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`](https://github.com/Light-Heart-Labs/DreamServer/blob/main/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`](https://github.com/Light-Heart-Labs/DreamServer/blob/main/manifest.yaml), [`manifest.yml`](https://github.com/Light-Heart-Labs/DreamServer/blob/main/manifest.yml), or [`manifest.json`](https://github.com/Light-Heart-Labs/DreamServer/blob/main/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`](https://github.com/Light-Heart-Labs/DreamServer/blob/main/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`](https://github.com/Light-Heart-Labs/DreamServer/blob/main/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:

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

```

### Step 2: Define the Service Manifest

Create a [`manifest.yaml`](https://github.com/Light-Heart-Labs/DreamServer/blob/main/manifest.yaml) file that declares the service metadata. The manifest must conform to `schema_version: dream.services.v1`:

```yaml
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`](https://github.com/Light-Heart-Labs/DreamServer/blob/main/dream-server/extensions/services/llama-server/manifest.yaml).

### Step 3: Create the Docker Compose Fragment

Add a [`compose.yaml`](https://github.com/Light-Heart-Labs/DreamServer/blob/main/compose.yaml) file that defines the container configuration. This fragment must obey the security constraints enforced by `_scan_user_compose_content()`:

```yaml
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:

- [`compose.nvidia.yaml`](https://github.com/Light-Heart-Labs/DreamServer/blob/main/compose.nvidia.yaml) for NVIDIA CUDA support
- [`compose.amd.yaml`](https://github.com/Light-Heart-Labs/DreamServer/blob/main/compose.amd.yaml) for AMD ROCm support

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()`:

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

```

Start the service using the CLI:

```bash
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`](https://github.com/Light-Heart-Labs/DreamServer/blob/main/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:

- **[`dream-server/lib/service-registry.sh`](https://github.com/Light-Heart-Labs/DreamServer/blob/main/dream-server/lib/service-registry.sh)** – Contains `sr_load()` (lines 54-108) for manifest parsing and `sr_compose_flags()` (lines 300-324) for generating Docker Compose command flags. Defines the associative arrays that store service metadata (lines 26-45).

- **[`dream-server/scripts/resolve-compose-stack.sh`](https://github.com/Light-Heart-Labs/DreamServer/blob/main/dream-server/scripts/resolve-compose-stack.sh)** – Implements the stack resolver (lines 50-124, 150-210) and the security scanner `_scan_user_compose_content()` (lines 111-246). References the core service whitelist at lines 176-187.

- **[`dream-server/config/core-service-ids.json`](https://github.com/Light-Heart-Labs/DreamServer/blob/main/dream-server/config/core-service-ids.json)** – JSON list of reserved service IDs that user extensions cannot claim.

- **[`dream-server/extensions/services/llama-server/manifest.yaml`](https://github.com/Light-Heart-Labs/DreamServer/blob/main/dream-server/extensions/services/llama-server/manifest.yaml)** – Reference implementation of a built-in service manifest.

## 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`](https://github.com/Light-Heart-Labs/DreamServer/blob/main/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`](https://github.com/Light-Heart-Labs/DreamServer/blob/main/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`](https://github.com/Light-Heart-Labs/DreamServer/blob/main/manifest.yaml) and a Compose fragment.
- **GPU support** is handled through optional overlay files ([`compose.nvidia.yaml`](https://github.com/Light-Heart-Labs/DreamServer/blob/main/compose.nvidia.yaml), [`compose.amd.yaml`](https://github.com/Light-Heart-Labs/DreamServer/blob/main/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`](https://github.com/Light-Heart-Labs/DreamServer/blob/main/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`](https://github.com/Light-Heart-Labs/DreamServer/blob/main/compose.nvidia.yaml) or [`compose.amd.yaml`](https://github.com/Light-Heart-Labs/DreamServer/blob/main/compose.amd.yaml)) based on the currently detected backend. If no GPU overlay exists, the base [`compose.yaml`](https://github.com/Light-Heart-Labs/DreamServer/blob/main/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`](https://github.com/Light-Heart-Labs/DreamServer/blob/main/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.