Hosting Provider Integration Guide

This guide walks you through integrating @better-openclaw/core into your hosting platform to offer users a marketplace of 180+ self-hosted services. The addon stack API handles Docker Compose generation, secret management, port allocation, and skill configuration — your platform just needs to orchestrate the output.

Architecture Overview

The integration follows a clean separation of concerns:

  • Your platform manages infrastructure: servers, PostgreSQL, Redis, reverse proxy, and the OpenClaw gateway.
  • @better-openclaw/core generates addon layers: Docker Compose overlays, environment files, proxy routes, and skill definitions.
┌─────────────────────────────────────────────┐
│              Your Hosting Platform           │
│                                              │
│  ┌──────────┐  ┌──────────┐  ┌───────────┐  │
│  │ Dashboard │  │ API/CLI  │  │ Provision  │  │
│  │   (UI)   │→ │ Backend  │→ │  Engine    │  │
│  └──────────┘  └────┬─────┘  └─────┬─────┘  │
│                     │              │         │
│              ┌──────┴──────┐       │         │
│              │ @better-    │       │         │
│              │ openclaw/   │       │         │
│              │ core        │       │         │
│              └──────┬──────┘       │         │
│                     │              │         │
│              ┌──────┴──────────────┴──┐      │
│              │     Docker Host        │      │
│              │  ┌─────┐ ┌──────────┐  │      │
│              │  │Infra│ │ Addons   │  │      │
│              │  │Stack│ │ Overlay  │  │      │
│              │  └─────┘ └──────────┘  │      │
│              └────────────────────────┘      │
└─────────────────────────────────────────────┘

Step 1: Install the Core Package

npm install @better-openclaw/core

The package is a pure TypeScript library with no native dependencies. It works in Node.js, Deno, Bun, and edge runtimes.

Step 2: Generate the Initial Stack

When a user selects services from your marketplace, call generateAddonStack():

import { generateAddonStack } from "@better-openclaw/core";

async function provisionAddons(userId: string, services: string[]) {
  // Your infrastructure's occupied ports
  const reservedPorts = [80, 443, 3000, 3456, 5432, 6379, 8080];

  const result = generateAddonStack({
    instanceId: `user-${userId}`,
    services,
    reservedPorts,
    generateSecrets: true,
    platform: "linux/amd64",
  });

  // Handle skipped services (missing credentials, no GPU, etc.)
  for (const skipped of result.metadata.skippedServices) {
    if (skipped.reason === "missing_credentials") {
      // Prompt user for API keys
      await requestCredentials(userId, skipped.serviceId, skipped.requiredCredentials);
      return;
    }
    if (skipped.reason === "gpu_required") {
      // Show upgrade prompt
      await suggestGpuPlan(userId, skipped.serviceId);
    }
  }

  // Write files to the instance directory
  await writeFile(`/instances/${userId}/docker-compose.override.yml`, result.composeOverride);
  await writeFile(`/instances/${userId}/.env.addons`, result.envFile);

  // Write skill files
  for (const [path, content] of Object.entries(result.skillFiles)) {
    await writeFile(`/instances/${userId}/skills/${path}`, content);
  }

  // Register proxy routes with your reverse proxy
  await registerProxyRoutes(userId, result.proxyRoutes);

  // Merge openclaw.json config
  await patchOpenclawConfig(userId, result.openclawConfigPatch);

  // Start the addon containers
  await exec(`docker compose -f docker-compose.override.yml up -d`, {
    cwd: `/instances/${userId}`,
  });

  return {
    services: result.metadata.resolvedServices,
    ports: result.metadata.portAssignments,
    warnings: result.warnings,
  };
}

Step 3: Handle Service Updates

When users add or remove services from their running instance, use updateAddonStack() instead of regenerating from scratch:

import { updateAddonStack } from "@better-openclaw/core";

async function updateUserAddons(
  userId: string,
  addServices: string[],
  removeServices: string[],
) {
  // Read current state
  const currentCompose = await readFile(`/instances/${userId}/docker-compose.override.yml`);
  const currentEnv = await readFile(`/instances/${userId}/.env.addons`);

  const result = updateAddonStack({
    instanceId: `user-${userId}`,
    currentCompose,
    currentEnv,
    addServices,
    removeServices,
    generateSecrets: true,
  });

  // Pull new images before stopping old services
  for (const image of result.imagesToPull) {
    await exec(`docker pull ${image}`);
  }

  // Write updated files (env values for existing services are preserved)
  await writeFile(`/instances/${userId}/docker-compose.override.yml`, result.composeOverride);
  await writeFile(`/instances/${userId}/.env.addons`, result.envFile);

  // Update proxy routes
  await registerProxyRoutes(userId, result.proxyRoutes);

  // Recreate only changed services
  await exec(`docker compose up -d --remove-orphans`, {
    cwd: `/instances/${userId}`,
  });

  return {
    added: result.metadata.added,
    removed: result.metadata.removed,
    unchanged: result.metadata.unchanged,
  };
}

Step 4: Integrate Proxy Routes

Each addon service that exposes a web UI gets a proxy route. Integrate these with your reverse proxy:

// Caddy integration example
function generateCaddySnippet(routes: ProxyRoute[]): string {
  return routes.map(route => {
    const upstream = `${route.serviceId}:${route.port}`;
    if (route.stripPrefix) {
      return `handle_path ${route.path}/* {
  reverse_proxy ${upstream}
}`;
    }
    return `handle ${route.path}/* {
  reverse_proxy ${upstream}
}`;
  }).join("\n\n");
}

// Traefik integration example
function generateTraefikLabels(route: ProxyRoute): Record<string, string> {
  const name = route.serviceId.replace(/-/g, "");
  return {
    [`traefik.http.routers.${name}.rule`]:
      `PathPrefix(\`${route.path}\`)`,
    [`traefik.http.services.${name}.loadbalancer.server.port`]:
      String(route.port),
  };
}

Step 5: Present the Service Catalog

Use the service registry to build your marketplace UI:

import {
  getAllServices,
  getServicesByCategory,
  SERVICE_CATEGORIES,
} from "@better-openclaw/core";

// Get all 180+ services with metadata
const services = getAllServices();

// Group by category for UI tabs
for (const cat of SERVICE_CATEGORIES) {
  const categoryServices = getServicesByCategory(cat.id);
  console.log(`${cat.icon} ${cat.name}: ${categoryServices.length} services`);
}

// Each service definition includes:
// - id, name, description, category
// - image (Docker image), ports, volumes
// - environment variables with descriptions
// - memoryMB (estimated RAM usage)
// - maturity ("stable" | "beta" | "experimental")
// - proxyPath (reverse proxy path)
// - capDropCompatible (security hardening support)

Step 6: Offer Skill Packs

Skill packs bundle multiple services into task-focused capabilities:

import {
  getAllSkillPacks,
  getCompatibleSkillPacks,
} from "@better-openclaw/core";

// Get skill packs compatible with user's current services
const packs = getCompatibleSkillPacks(["n8n", "qdrant"]);

// Each pack includes:
// - id, name, description
// - requiredServices: string[]
// - optionalServices: string[]
// - skills: SkillBinding[] (MCP tool definitions)

Security Best Practices

  • Always validate input with AddonStackInputSchema.parse() before passing user data to generateAddonStack().
  • Never expose secrets — the envFile contains generated passwords. Store it securely and never return it to the frontend.
  • Use reserved ports to prevent addon services from claiming ports your infrastructure needs.
  • Review skipped services — a missing_credentials skip means the service needs user-provided API keys. Don't deploy it without them.
  • Network isolation — the generated compose uses openclaw-network as an external network. Ensure your Docker network configuration restricts inter-service communication appropriately.

Database Integration

Services that need PostgreSQL (like n8n, Grafana, Gitea) automatically get a postgres-setup init container that:

  1. Connects to your existing PostgreSQL instance
  2. Creates a dedicated database and user for each service
  3. Uses idempotent SQL ("IF NOT EXISTS") for safe re-runs
  4. Generates unique passwords per service

Your PostgreSQL must be reachable at the hostname postgresql on the Docker network. The init container uses the POSTGRES_USER and POSTGRES_PASSWORD from the infrastructure's environment.

Monitoring Integration

Each service definition includes a healthCheck configuration. Use it to set up monitoring:

import { getServiceById } from "@better-openclaw/core";

const n8n = getServiceById("n8n");
console.log(n8n?.healthCheck);
// {
//   test: ["CMD-SHELL", "wget -qO- http://localhost:5678/healthz || exit 1"],
//   interval: "30s",
//   timeout: "10s",
//   retries: 3,
//   startPeriod: "30s"
// }

See the Addon Stack API Reference for complete parameter and return type documentation.