deploy_compose
ActiveTool of cloud.redu/mcp
Deploys a MULTI-CONTAINER app — a repo that ships a docker-compose.yml / compose.yaml (app + its own db/redis/worker containers) — onto ONE VM via podman-compose, and exposes ONE service at https://<name>-<id>.redu.cloud. Use this instead of deploy_app when the repo is a compose stack rather than a single Dockerfile. SAME prereqs + source modes as deploy_app: run check_deploy_prerequisites (network_id + keypair_name), then GIT (`repo`, +git_token for private) or UPLOAD (prepare_upload → source_token). PORT: pass the HOST port the exposed service publishes (the LEFT side of its `ports:` mapping) — redu probes + proxies that exact port; pass `service` to name which service it is (plan_deploy detects both). DB: 'compose' (default) uses the stack's own db service (self-contained); 'single_vm'/'managed' provision a Postgres/MySQL and APPEND its conn env (DATABASE_URL/PG*/MYSQL_*) to the project .env — your compose must REFERENCE those vars to use it (we never rewrite your compose file). Build+provision can take 4-40 min (it pulls/builds every service — heavy ClickHouse/Kafka stacks are slow); poll get_deployment until status='ready', and on failure read build_log (it captures podman-compose logs). TIPS: (1) prefer the project's PREBUILT published images — swap any `build:` block for the published `image:` tag (building from source on the VM is less reliable). (2) redu injects APP_URL/PUBLIC_URL (= the app's public URL) into the env — map the app's own URL/cookie-domain var (SERVER_URL/NEXTAUTH_URL/…) to ${PUBLIC_URL}. (3) multi-surface apps (dashboard + API on separate ports) → pass `expose:[{port,service},…]`, each gets its own URL. (4) if the stack needs a ONE-TIME DB migrate/prepare before it serves (Rails `rails db:prepare`, Django `migrate`, Prisma `migrate deploy` — e.g. Lago), pass `migrate_command` (+ `migrate_service`); without it the stack deploys to 'ready' but 502s on real use because the schema is missing. ALWAYS run plan_deploy first and confirm the plan + cost with the user.
Parameters schema
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"name",
"keypair_name"
],
"properties": {
"env": {
"type": "object",
"description": "Env vars APPENDED to the compose project's .env (for ${VAR} interpolation). We never rewrite your compose file.",
"additionalProperties": {
"type": "string"
}
},
"name": {
"type": "string",
"maxLength": 63,
"minLength": 1,
"description": "Name for the deployment / VM (lowercase letters, numbers, hyphens)."
},
"port": {
"type": "integer",
"default": 8080,
"maximum": 65535,
"minimum": 1,
"description": "REQUIRED in practice: the HOST port the exposed service publishes (the LEFT side of its compose `ports:` mapping) — redu health-probes + proxies THIS port, so a wrong value fails the deploy. plan_deploy detects it."
},
"repo": {
"type": "string",
"format": "uri",
"description": "GIT MODE: public git repo URL that ships a docker-compose file. Private repo also needs git_token. Omit when using source_token."
},
"dname": {
"type": "string",
"description": "Custom *.redu.cloud subdomain. For a STABLE, KNOWN-AHEAD URL (needed when the app must be told its OWN url — OAuth callbacks, cookie domain, a frontend that calls its API), generate the FULL auto-gen form yourself: `<label>-<8 lowercase letters/digits>.redu.cloud` (e.g. `myapp-7k2m9x4p.redu.cloud`) — that exact form is used VERBATIM — and wire the app's own URL env to that SAME value. A BARE `<label>.redu.cloud` is NOT used as-is: redu appends a random 8-char suffix for uniqueness, so anything wired to the bare name will NOT match the real URL. Omit to auto-generate (then read the real URL from get_deployment)."
},
"redis": {
"enum": [
"none",
"managed"
],
"type": "string",
"description": "'managed' = a SEPARATE managed Redis VM, auto-provisioned + wired; its REDIS_URL/REDIS_* is APPENDED to the project .env — your compose service must REFERENCE it to use it (we never rewrite your compose file). Omit/'none' to use the stack's own redis container. With 'managed' you do NOT call create_redis."
},
"expose": {
"type": "array",
"items": {
"type": "object",
"required": [
"port"
],
"properties": {
"port": {
"type": "integer",
"maximum": 65535,
"minimum": 1
},
"dname": {
"type": "string"
},
"service": {
"type": "string"
}
},
"additionalProperties": false
},
"description": "MULTI-SURFACE apps: expose several services, each gets its OWN *.redu.cloud URL. The FIRST entry is the primary one redu health-gates (its port overrides `port`). CRITICAL when one surface must KNOW another's URL (a frontend that calls its API — e.g. Lago's front -> API): GENERATE each surface's dname up front as `<label>-<8 lowercase letters/digits>.redu.cloud` (used VERBATIM) and wire the app's cross-surface URL env (LAGO_API_URL, LAGO_FRONT_URL, FRONTEND_URL, NEXT_PUBLIC_API_URL, …) to those EXACT values. Do NOT use a bare `<label>.redu.cloud` — redu appends a random suffix, so config wired to the bare name breaks."
},
"subdir": {
"type": "string",
"description": "Directory within the source that contains the compose file (if not at the root)."
},
"db_name": {
"type": "string",
"description": "DB name for single_vm/managed (default 'app')."
},
"db_user": {
"type": "string",
"description": "DB user for single_vm/managed (default 'appuser')."
},
"git_ref": {
"type": "string",
"description": "git mode only: branch/tag/commit (default: the repo's default branch)."
},
"service": {
"type": "string",
"description": "The compose SERVICE to expose at the public URL (informational; the exposed port is `port`)."
},
"database": {
"enum": [
"compose",
"single_vm",
"managed"
],
"type": "string",
"description": "DB mode: 'compose' (default) = use the compose file's OWN db service (self-contained, nothing extra provisioned); 'single_vm' = Postgres ON the app VM; 'managed' = a SEPARATE managed-PG/MySQL VM. For single_vm/managed the conn env (DATABASE_URL/PG*/MYSQL_*) is APPENDED to the project .env — your compose service must REFERENCE it (e.g. environment: - DATABASE_URL=${DATABASE_URL}) to actually use it; we never modify your compose file. Choose engine with db_engine."
},
"db_engine": {
"enum": [
"postgres",
"mysql",
"mariadb"
],
"type": "string",
"description": "managed only: 'postgres' (default) → PG* env; 'mysql'/'mariadb' → MYSQL_* env. mysql/mariadb require database:'managed'."
},
"flavor_id": {
"type": "string",
"default": "3",
"minLength": 1,
"description": "App VM size — from list_flavors. A multi-container stack often wants m1.large+; plan_deploy sizes it."
},
"git_token": {
"type": "string",
"description": "git mode only: token to clone a PRIVATE repo."
},
"db_version": {
"type": "string",
"description": "DB version for single_vm/managed (Postgres '16'|'15'|'14'; MySQL '8.0'; MariaDB '11.4')."
},
"network_id": {
"type": "string",
"minLength": 1,
"description": "Private network id — auto-selected from check_deploy_prerequisites if omitted."
},
"compose_file": {
"type": "string",
"description": "Path within the source to the compose file (e.g. 'deploy/docker-compose.yml'). Auto-detected (docker-compose.yml / compose.yaml / …) if omitted."
},
"db_flavor_id": {
"type": "string",
"description": "managed only: the dedicated DB VM size (from list_flavors). Defaults to the app flavor."
},
"db_superuser": {
"type": "boolean",
"description": "single_vm/managed Postgres: grant the DB user SUPERUSER (dedicated DB VM, so safe)."
},
"keypair_name": {
"type": "string",
"minLength": 1,
"description": "REQUIRED. An EXISTING SSH keypair name — from list_keypairs / import_keypair."
},
"source_token": {
"type": "string",
"minLength": 16,
"description": "UPLOAD MODE: token from prepare_upload — deploys an uploaded tarball of your LOCAL dir (no git). Omit `repo` when set."
},
"db_extensions": {
"type": "array",
"items": {
"enum": [
"vector",
"pgvector",
"postgis",
"pgaudit",
"pg_stat_statements",
"hstore",
"pg_trgm",
"uuid-ossp",
"citext",
"pgcrypto",
"ltree",
"btree_gin",
"btree_gist"
],
"type": "string"
},
"description": "single_vm/managed Postgres: extensions to pre-install (pgvector, postgis, pgaudit, …)."
},
"redis_version": {
"enum": [
"7",
"6"
],
"type": "string",
"description": "managed Redis only: version (default '7')."
},
"compose_engine": {
"enum": [
"auto",
"docker",
"podman"
],
"type": "string",
"description": "Container engine for the compose build (default: the server's choice, usually podman). Set 'docker' to force the docker engine."
},
"redis_password": {
"type": "string",
"description": "managed Redis only: a specific password to set (otherwise auto-generated)."
},
"idempotency_key": {
"type": "string",
"minLength": 8
},
"migrate_command": {
"type": "string",
"description": "One-time DB prepare/migrate/seed, run AFTER `up -d` and BEFORE the app is marked ready — redu runs `podman-compose run --rm <migrate_service|service> <cmd>` (Rails `bundle exec rails db:prepare`, Django `python manage.py migrate`, Prisma `prisma migrate deploy`). REQUIRED for any stack whose schema is NOT auto-created on boot (e.g. Lago): WITHOUT it the stack deploys and flips to 'ready' but 502s on real use because the DB schema is missing."
},
"migrate_service": {
"type": "string",
"description": "Compose service to run migrate_command in (defaults to `service`)."
},
"redis_flavor_id": {
"type": "string",
"description": "managed Redis only: the dedicated Redis VM size (from list_flavors). Defaults to the app flavor."
},
"security_group_names": {
"type": "array",
"items": {
"type": "string"
},
"default": [
"default"
]
}
},
"additionalProperties": false
}No endpoints wrapped at confidence ≥ 0.70.
Parent server
cloud.redu/mcp
1/7 registries