docker-compose_swarm.yml
Rama main · réplica de lectura del repositorio original.
services:
db-prod:
container_name: alexclaw-db-prod
image: pgvector/pgvector:pg17
restart: unless-stopped
environment:
# The owner. Used only when the volume is first initialised; the
# application role is created by the operator — see the upgrade guide.
POSTGRES_USER: ${DATABASE_OWNER_USERNAME:-alexclaw}
POSTGRES_PASSWORD: ${DATABASE_OWNER_PASSWORD}
POSTGRES_DB: alex_claw_prod
# For db-init/01-app-role.sh, which creates the application role on a
# fresh volume. An existing volume ignores both.
DATABASE_APP_USERNAME: "${DATABASE_USERNAME:?not set - the application role}"
DATABASE_APP_PASSWORD: "${DATABASE_PASSWORD:?not set - the application role's password}"
volumes:
- pgdata:/var/lib/postgresql/data
- ./db-init/01-app-role.sh:/docker-entrypoint-initdb.d/01-app-role.sh:ro
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DATABASE_OWNER_USERNAME:-alexclaw}"]
interval: 5s
timeout: 3s
retries: 5
# Migrations run here, as the database owner, and nowhere else. This is the
# only container given the owner's credentials: it migrates, grants the
# application role its privileges (AlexClaw.Database.Roles), and exits. The
# application starts only once it has exited cleanly, and connects as the
# application role — which it checks at boot.
migrate:
container_name: alexclaw-migrate
image: alexclaw-app:latest
build:
context: .
args:
BUILD_NUMBER: ${BUILD_NUMBER:-0}
command: ["bin/alex_claw", "eval", "AlexClaw.Release.migrate()"]
restart: "no"
read_only: true
tmpfs:
- /tmp
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
depends_on:
db-prod:
condition: service_healthy
environment:
DATABASE_USERNAME: ${DATABASE_OWNER_USERNAME:-alexclaw}
DATABASE_PASSWORD: "${DATABASE_OWNER_PASSWORD:?not set - the database owner's password}"
DATABASE_HOSTNAME: db-prod
DATABASE_APP_USERNAME: "${DATABASE_USERNAME:?not set - the application role}"
# No SECRET_KEY_BASE, deliberately: the owner's credentials and the key
# that decrypts the stored secrets never share a container. The runtime
# configuration accepts its absence for an eval only.
CLUSTER_COOKIE: "${CLUSTER_COOKIE:?not set - generate one with: openssl rand -base64 32}"
RELEASE_TMP: /tmp
ERL_CRASH_DUMP: /tmp/erl_crash.dump
node1:
# The BEAM turns SIGTERM into an orderly System.stop/0, but Docker's default
# grace period is 10 seconds and the supervision tree needs longer: every
# stop was reaching SIGKILL, so Reasoning.Loop's terminate/2 never ran and
# in-flight workflow runs stayed recorded as executing.
stop_grace_period: 30s
read_only: true
tmpfs:
- /tmp
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
image: alexclaw-app:latest
build:
context: .
args:
BUILD_NUMBER: ${BUILD_NUMBER:-0}
hostname: node1.local
restart: unless-stopped
# A ceiling, so a node can never take the host down with it. Local models
# run on the host, outside this limit.
mem_limit: 2g
cpus: 2.0
depends_on:
migrate:
condition: service_completed_successfully
ports:
- "${ADMIN_BIND:-127.0.0.1}:5001:5001"
extra_hosts:
- "host.docker.internal:host-gateway"
environment:
# The application role, never the owner: the boot refuses the owner.
DATABASE_USERNAME: "${DATABASE_USERNAME:?not set - the application role}"
DATABASE_PASSWORD: ${DATABASE_PASSWORD}
DATABASE_HOSTNAME: db-prod
SECRET_KEY_BASE: ${SECRET_KEY_BASE}
ADMIN_PASSWORD: ${ADMIN_PASSWORD}
# The published port above: the origin the browser sends.
ADMIN_PORT: "5001"
PHX_HOST: ${PHX_HOST:-}
CHECK_ORIGIN: ${CHECK_ORIGIN:-}
TELEGRAM_CHAT_ID: ${TELEGRAM_CHAT_ID}
OLLAMA_ENABLED: ${OLLAMA_ENABLED:-false}
OLLAMA_HOST: ${OLLAMA_HOST:-http://host.docker.internal:11434}
OLLAMA_MODEL: ${OLLAMA_MODEL:-}
LMSTUDIO_ENABLED: ${LMSTUDIO_ENABLED:-false}
LMSTUDIO_HOST: ${LMSTUDIO_HOST:-http://host.docker.internal:1234}
LMSTUDIO_MODEL: ${LMSTUDIO_MODEL:-}
NODE_NAME: alexclaw@node1.local
CLUSTER_COOKIE: "${CLUSTER_COOKIE:?not set - generate one with: openssl rand -base64 32}"
SKILLS_DIR: /app/skills
RELEASE_TMP: /tmp
ERL_CRASH_DUMP: /tmp/erl_crash.dump
volumes:
- skills_data:/app/skills
node2:
# The BEAM turns SIGTERM into an orderly System.stop/0, but Docker's default
# grace period is 10 seconds and the supervision tree needs longer: every
# stop was reaching SIGKILL, so Reasoning.Loop's terminate/2 never ran and
# in-flight workflow runs stayed recorded as executing.
stop_grace_period: 30s
read_only: true
tmpfs:
- /tmp
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
image: alexclaw-app:latest
build:
context: .
args:
BUILD_NUMBER: ${BUILD_NUMBER:-0}
hostname: node2.local
restart: unless-stopped
# A ceiling, so a node can never take the host down with it. Local models
# run on the host, outside this limit.
mem_limit: 2g
cpus: 2.0
depends_on:
migrate:
condition: service_completed_successfully
ports:
- "${ADMIN_BIND:-127.0.0.1}:5002:5001"
extra_hosts:
- "host.docker.internal:host-gateway"
environment:
# The application role, never the owner: the boot refuses the owner.
DATABASE_USERNAME: "${DATABASE_USERNAME:?not set - the application role}"
DATABASE_PASSWORD: ${DATABASE_PASSWORD}
DATABASE_HOSTNAME: db-prod
SECRET_KEY_BASE: ${SECRET_KEY_BASE}
ADMIN_PASSWORD: ${ADMIN_PASSWORD}
# The published port above: the origin the browser sends.
ADMIN_PORT: "5002"
PHX_HOST: ${PHX_HOST:-}
CHECK_ORIGIN: ${CHECK_ORIGIN:-}
TELEGRAM_CHAT_ID: ${TELEGRAM_CHAT_ID}
OLLAMA_ENABLED: ${OLLAMA_ENABLED:-false}
OLLAMA_HOST: ${OLLAMA_HOST:-http://host.docker.internal:11434}
OLLAMA_MODEL: ${OLLAMA_MODEL:-}
LMSTUDIO_ENABLED: ${LMSTUDIO_ENABLED:-false}
LMSTUDIO_HOST: ${LMSTUDIO_HOST:-http://host.docker.internal:1234}
LMSTUDIO_MODEL: ${LMSTUDIO_MODEL:-}
NODE_NAME: alexclaw@node2.local
CLUSTER_COOKIE: "${CLUSTER_COOKIE:?not set - generate one with: openssl rand -base64 32}"
SKILLS_DIR: /app/skills
RELEASE_TMP: /tmp
ERL_CRASH_DUMP: /tmp/erl_crash.dump
volumes:
- skills_data:/app/skills
volumes:
pgdata:
skills_data: