docker-compose.cloud.yml
Rama main · réplica de lectura del repositorio original.
# ANT — self-hosted cloud override (multi-user, Google sign-in, NO billing).
#
# docker compose -f docker-compose.yml -f docker-compose.cloud.yml up -d
#
# Requires a .env (or exported shell vars) with at least:
# ANT_JWT_PUBLIC_KEY + ANT_JWT_PRIVATE_KEY (see below),
# GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET
# See docs/guides/self-host-cloud.md for the full checklist.
#
# YAML anchors are file-local, so this file cannot merge into the base's
# `x-ant-env-common` anchor. Instead each ant service sets its own
# `environment:` keys — compose merges per-service environment maps key-wise,
# so ANT_REDIS_URL / ANT_WORKSPACE_BASE_PATH etc. from the base still apply.
#
# ANT_REQUIRE_BILLING is deliberately NOT set here — it is reserved for the
# managed deployment (fails boot when the @ant/cloud billing overlay is
# absent). Self-hosted cloud runs unmetered.
#
# Secrets are scoped to the services that CONSUME them, not applied uniformly.
# ant-preview spawns user-authored install and dev commands, and those children
# share its UID and process namespace — so anything in that service's
# environment is reachable from user code via /proc. Sign-in secrets have no
# consumer there, so they are not granted there (report C-001).
#
# Sessions are ES256 only. Generate a P-256 pair once and put the private half
# on ant-api only:
#
# openssl ecparam -genkey -name prime256v1 -noout \
# | openssl pkcs8 -topk8 -nocrypt -out jwt-private.pem
# openssl ec -in jwt-private.pem -pubout -out jwt-public.pem
# ANT_JWT_PRIVATE_KEY="$(cat jwt-private.pem)"
# ANT_JWT_PUBLIC_KEY="$(cat jwt-public.pem)"
#
# Child UID isolation (ANT_CHILD_UID/GID below) is the complementary control.
# Every ant service: mode + the origin CORS and cookie derivation read.
x-ant-env-cloud-base: &ant-env-cloud-base
ANT_SERVER_MODE: cloud
FRONTEND_URL: ${FRONTEND_URL:-http://localhost:4200}
# Session VERIFICATION (ant-realtime, ant-preview). The ES256 public key
# carries no minting authority — safe even where user-authored code can read
# the env via /proc (C-001, M-NEW-013).
x-ant-env-cloud-verify: &ant-env-cloud-verify
ANT_JWT_PUBLIC_KEY: ${ANT_JWT_PUBLIC_KEY:-}
# Session SIGNING. Consumed ONLY by ant-api, the only process that mounts the
# auth router and therefore the only one that mints a session.
x-ant-env-cloud-sign: &ant-env-cloud-sign
ANT_JWT_PRIVATE_KEY: ${ANT_JWT_PRIVATE_KEY:-}
# Sign-in and administration. Consumed ONLY by ant-api, which is the only
# process that mounts the auth and admin routers.
x-ant-env-cloud-identity: &ant-env-cloud-identity
GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID:?set GOOGLE_CLIENT_ID in .env (Google Cloud Console OAuth client)}
GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET:?set GOOGLE_CLIENT_SECRET in .env (Google Cloud Console OAuth client)}
GOOGLE_REDIRECT_URI: ${GOOGLE_REDIRECT_URI:-} # empty = derived as ${FRONTEND_URL}/api/auth/google/callback
ANT_SUPER_ADMIN_EMAILS: ${ANT_SUPER_ADMIN_EMAILS:-}
services:
ant-api:
environment:
<<: [*ant-env-cloud-base, *ant-env-cloud-verify, *ant-env-cloud-sign, *ant-env-cloud-identity]
ant-realtime:
environment:
<<: [*ant-env-cloud-base, *ant-env-cloud-verify]
# No HTTP surface → verifies nothing, signs nothing.
#
# Cloud mode REFUSES user-authored spawns (run_command, stdio MCP) unless
# children can be dropped to a different UID *and GID* (childIdentity gate).
# The image ships the `ant-child` account (uid/gid 10001, no login); changing
# UID/GID needs CAP_SETUID/CAP_SETGID, which a non-root service does not have —
# so the two user-code services run as container root and every user-authored
# child is dropped to ant-child with its supplementary groups cleared.
#
# ANT_CHILD_GID MUST be non-empty: an empty GID emits no setpriv --regid, and
# the dropped child keeps egid 0 + root's groups (H-014). It defaults to the
# ant-child gid; override both only to match a different image account.
#
# The credentialed dependency FETCH gets a THIRD identity (ant-acquire, 10002):
# it carries a user's registry PAT in its environment, and /proc/<pid>/environ
# is readable by any process sharing its UID — so it must not share the one
# every other user-code child runs under (M-NEW-001). Same group as ant-child
# so the installed tree stays writable by the credential-free lifecycle pass.
ant-job:
user: "0:0"
# bwrap builds each user-authored child's mount namespace from an
# unprivileged user namespace. Docker's default seccomp and AppArmor
# profiles refuse CLONE_NEWUSER without CAP_SYS_ADMIN, so lift them for the
# two services that run user code (cheaper than granting SYS_ADMIN).
security_opt:
- seccomp=unconfined
- apparmor=unconfined
environment:
<<: *ant-env-cloud-base
ANT_CHILD_UID: ${ANT_CHILD_UID:-10001}
ANT_CHILD_GID: ${ANT_CHILD_GID:-10001}
# Credentialed dependency-FETCH runs under its OWN uid so a same-UID peer
# cannot read the user's PAT from /proc/<pid>/environ (M-NEW-001).
ANT_CHILD_ACQUIRE_UID: ${ANT_CHILD_ACQUIRE_UID:-10002}
ANT_CHILD_ACQUIRE_GID: ${ANT_CHILD_ACQUIRE_GID:-10001}
# Verifies proxy ownership; never serves sign-in. Runs user-authored code, so
# it receives the public key ONLY.
ant-preview:
user: "0:0"
# bwrap builds each user-authored child's mount namespace from an
# unprivileged user namespace. Docker's default seccomp and AppArmor
# profiles refuse CLONE_NEWUSER without CAP_SYS_ADMIN, so lift them for the
# two services that run user code (cheaper than granting SYS_ADMIN).
security_opt:
- seccomp=unconfined
- apparmor=unconfined
environment:
<<: [*ant-env-cloud-base, *ant-env-cloud-verify]
ANT_CHILD_UID: ${ANT_CHILD_UID:-10001}
ANT_CHILD_GID: ${ANT_CHILD_GID:-10001}
# Credentialed dependency-FETCH runs under its OWN uid so a same-UID peer
# cannot read the user's PAT from /proc/<pid>/environ (M-NEW-001).
ANT_CHILD_ACQUIRE_UID: ${ANT_CHILD_ACQUIRE_UID:-10002}
ANT_CHILD_ACQUIRE_GID: ${ANT_CHILD_ACQUIRE_GID:-10001}