The composer generates Docker Compose YAML files from resolved service configurations, with support for single-file or multi-file layouts, security hardening, and platform-specific options.
Overview
The composer takes a ResolverOutput and generates complete Docker Compose configurations including:
- Service definitions with proper dependency ordering
- Volume and network declarations
- Environment variable substitution
- Health checks and restart policies
- Security hardening (capability dropping, no-new-privileges)
- GPU device reservations
- Reverse proxy labels (Traefik)
- Multi-file layouts with profiles
compose
Generates a single Docker Compose YAML string with all services.
import { compose } from '@better-openclaw/core';
import type { ResolverOutput, ComposeOptions } from '@better-openclaw/core';
const yaml: string = compose(resolved, {
projectName: 'my-stack',
proxy: 'caddy',
domain: 'example.com',
platform: 'linux/amd64',
openclawVersion: 'latest',
hardened: true
});
console.log(yaml);
Parameters
Output from the resolver containing validated services
Configuration options for Compose generation
Docker Compose project name
Reverse proxy type (“caddy”, “traefik”, “none”)
Custom HTTP port for proxy (default: 80)
Custom HTTPS port for proxy (default: 443)
Domain for reverse proxy routing
Enable NVIDIA GPU device reservations
options.platform
Platform
default:"linux/amd64"
Target platform (linux/amd64 or linux/arm64)
options.deployment
DeploymentTarget
default:"local"
Deployment target environment
OpenClaw gateway image version
options.bareMetalNativeHost
Add extra_hosts for native services on host
options.openclawImage
OpenclawImageVariant
default:"official"
OpenClaw image variant (“official”, “coolify”, “alpine”)
Apply security hardening (cap_drop, no-new-privileges)
options.openclawInstallMethod
OpenclawInstallMethod
default:"docker"
How to install OpenClaw (“docker” or “direct”)
Returns
Returns a complete Docker Compose YAML string ready to write to docker-compose.yml.
composeMultiFile
Generates multiple Docker Compose files with profile-based overrides by service category.
import { composeMultiFile } from '@better-openclaw/core';
import type { ComposeResult } from '@better-openclaw/core';
const result: ComposeResult = composeMultiFile(resolved, options);
console.log('Main file:', result.mainFile);
console.log('Files:', Object.keys(result.files));
console.log('Profiles:', result.profiles);
// Write files
for (const [filename, content] of Object.entries(result.files)) {
await writeFile(filename, content);
}
Parameters
Same as compose() function.
Returns
Map of filename to YAML content
docker-compose.yml - Base file with core services
docker-compose.*.yml - Profile-specific override files
Name of the main Compose file (always “docker-compose.yml”)
List of Docker Compose profiles used (e.g., [“ai”, “media”, “monitoring”])
Profile Mapping
Services are split into files based on category:
| Category | File | Profile |
|---|
| ai, ai-platform | docker-compose.ai.yml | ai |
| media | docker-compose.media.yml | media |
| monitoring, analytics | docker-compose.monitoring.yml | monitoring |
| dev-tools, coding-agent | docker-compose.tools.yml | tools |
| social-media | docker-compose.social.yml | social |
| knowledge | docker-compose.knowledge.yml | knowledge |
| communication | docker-compose.communication.yml | communication |
| others | docker-compose.yml | (none) |
Generated Structure
Service Definition
Each service gets:
services:
redis:
image: redis:7-alpine
environment:
REDIS_PASSWORD: ${REDIS_PASSWORD}
ports:
- "${REDIS_EXTERNAL_PORT:-6379}:6379"
volumes:
- redis-data:/data
healthcheck:
test: ["CMD-SHELL", "redis-cli ping"]
interval: 30s
timeout: 10s
retries: 3
restart: unless-stopped
networks:
- openclaw-network
cap_drop:
- ALL
security_opt:
- "no-new-privileges:true"
Gateway Services
When openclawInstallMethod: "docker":
services:
openclaw-gateway:
image: ${OPENCLAW_IMAGE:-ghcr.io/openclaw/openclaw:latest}
environment:
OPENCLAW_GATEWAY_TOKEN: ${OPENCLAW_GATEWAY_TOKEN}
# ... all AI provider keys
volumes:
- ${OPENCLAW_CONFIG_DIR:-./openclaw/config}:/home/node/.openclaw
- ${OPENCLAW_WORKSPACE_DIR:-./openclaw/workspace}:/home/node/.openclaw/workspace
ports:
- "${OPENCLAW_GATEWAY_PORT:-18789}:18789"
- "${OPENCLAW_BRIDGE_PORT:-18790}:18790"
networks:
- openclaw-network
restart: unless-stopped
depends_on:
postgresql: { condition: service_healthy }
redis: { condition: service_started }
command:
- node
- dist/index.js
- gateway
- --bind
- ${OPENCLAW_GATEWAY_BIND:-lan}
- --port
- "18789"
openclaw-cli:
image: ${OPENCLAW_IMAGE:-ghcr.io/openclaw/openclaw:latest}
environment:
OPENCLAW_GATEWAY_HOST: openclaw-gateway
OPENCLAW_GATEWAY_PORT: "18789"
OPENCLAW_GATEWAY_TOKEN: ${OPENCLAW_GATEWAY_TOKEN}
stdin_open: true
tty: true
networks:
- openclaw-network
depends_on:
openclaw-gateway: { condition: service_started }
entrypoint: ["node", "dist/index.js"]
restart: "no"
PostgreSQL Init Container
Automatically added when services need dedicated databases:
services:
postgres-setup:
image: postgres:17-alpine
depends_on:
postgresql: { condition: service_healthy }
environment:
PGHOST: postgresql
PGUSER: ${POSTGRES_USER:-openclaw}
PGDATABASE: ${POSTGRES_DB:-openclaw}
PGPASSWORD: ${POSTGRES_PASSWORD}
N8N_DB_PASSWORD: ${N8N_DB_PASSWORD}
entrypoint: ["/bin/sh", "-c"]
command:
- |
echo '=== PostgreSQL database setup ==='
psql -c "CREATE ROLE n8n_user WITH LOGIN PASSWORD '$N8N_DB_PASSWORD'"
psql -c "CREATE DATABASE n8n OWNER n8n_user"
psql -c "GRANT ALL PRIVILEGES ON DATABASE n8n TO n8n_user"
echo '=== All databases ready ==='
restart: "no"
networks:
- openclaw-network
Security Hardening
When hardened: true (default):
cap_drop: ["ALL"] - Drop all Linux capabilities
security_opt: ["no-new-privileges:true"] - Prevent privilege escalation
cap_add - Re-add specific capabilities only when needed:
caddy, traefik: ["NET_BIND_SERVICE"]
crowdsec: ["NET_BIND_SERVICE", "DAC_READ_SEARCH"]
- Memory limits set to 2x
minMemoryMB as safe limit
GPU Support
When gpu: true and service has gpuRequired: true:
services:
ollama:
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
Port Overrides
Customize host ports via portOverrides:
const yaml = compose(resolved, {
projectName: 'stack',
portOverrides: {
grafana: {
3000: 3150 // Map host 3150 -> container 3000
}
}
});
Environment Variables
All service environment variables use ${VAR_NAME} substitution from .env file:
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_USER: ${POSTGRES_USER:-openclaw}
Port mappings use _EXTERNAL_PORT suffix to avoid conflicts:
ports:
- "${GRAFANA_EXTERNAL_PORT:-3000}:3000"
Examples
Single File Generation
import { resolve, compose } from '@better-openclaw/core';
const resolved = resolve({
services: ['redis', 'postgresql'],
proxy: 'caddy',
platform: 'linux/amd64'
});
const yaml = compose(resolved, {
projectName: 'my-stack',
proxy: 'caddy',
domain: 'example.com',
platform: 'linux/amd64',
hardened: true
});
await writeFile('docker-compose.yml', yaml);
Multi-File with Profiles
import { composeMultiFile } from '@better-openclaw/core';
const result = composeMultiFile(resolved, options);
// Write all files
for (const [filename, content] of Object.entries(result.files)) {
await writeFile(filename, content);
}
// Generate .env with profiles
const envContent = [
`COMPOSE_FILE=${Object.keys(result.files).join(':')}`,
`COMPOSE_PROFILES=${result.profiles.join(',')}`,
// ... other vars
].join('\n');
await writeFile('.env', envContent);
Direct Install (No Docker Gateway)
const yaml = compose(resolved, {
projectName: 'stack',
openclawInstallMethod: 'direct', // No gateway/CLI containers
platform: 'linux/amd64'
});
// Result: only companion services (redis, postgresql, etc.)
// OpenClaw runs directly on host
const yaml = compose(resolved, {
projectName: 'stack',
bareMetalNativeHost: true, // Gateway can reach host services
platform: 'linux/amd64'
});
// Gateway gets extra_hosts: ["host.docker.internal:host-gateway"]
Types
ComposeOptions
interface ComposeOptions {
projectName: string;
proxy?: ProxyType;
proxyHttpPort?: number;
proxyHttpsPort?: number;
portOverrides?: Record<string, Record<string, number>>;
domain?: string;
gpu?: boolean;
platform?: Platform;
deployment?: DeploymentTarget;
openclawVersion?: string;
bareMetalNativeHost?: boolean;
openclawImage?: OpenclawImageVariant;
hardened?: boolean;
openclawInstallMethod?: OpenclawInstallMethod;
}
ComposeResult
interface ComposeResult {
files: Record<string, string>; // filename -> YAML content
mainFile: string; // "docker-compose.yml"
profiles: string[]; // ["ai", "media", ...]
}
See Also