Skip to Content
Deployment

Deployment

Magic Core is orchestrated with docker-compose. Two compose files cover two purposes:

FilePurposeServices
docker-compose.ymllocal dev infrastructurePostgres + Redis only (apps run via pnpm)
docker-compose.prod.ymlproductionPostgres + Redis + cliproxy + platform-api + agent-service + relay + keeper + gateway + nginx

Production topology

:80 ┌──────────────┐ client ──▶│ nginx │ forwards everything to gateway └──────┬───────┘ ┌──────────────┐ │ gateway │ routes by path prefix (health-aware) │ :3110 │ └──────┬───────┘ ┌────────────┬─────┴──────┬────────────┐ ▼ ▼ ▼ ▼ ┌──────────┐ ┌──────────┐ ┌────────┐ ┌────────┐ │platform- │ │ agent- │ │ relay │ │ keeper │ │api :3111 │ │svc :3112 │ │ :3113 │ │ :3114 │ └────┬─────┘ └────┬─────┘ └───┬────┘ └───┬────┘ │ │ ▼ ▼ │ │ ┌─────────────────────┐ │ └─────▶│ cliproxy :8317 │ account pool (internal) │ └─────────────────────┘ └───────────┬───────────────┘ ┌──────────────────┐ │ Postgres + Redis │ └──────────────────┘
ServicePortPublicNotes
nginx80the single entry point; reverse-proxies everything to gateway
gateway3110via nginxroutes by path prefix and verifies edge identity
platform-api3111via nginxCRUD + billing + admin console; the only migration writer
agent-service3112via nginxAI / SSE / realtime WS
relay3113via nginxOpenAI-compatible /v1 over the account pool
keeper3114via nginxaccount-pool worker + /api/admin/keeper admin API
cliproxy8317CLIProxyAPI account pool; internal-only, no published ports
Postgres / Redis5432 / 6379data / cache

Magic Core images are published to GHCR: ghcr.io/linmoqc/magic-core/<service>:${MAGIC_CORE_IMAGE_TAG:-main}. cliproxy uses the upstream image ghcr.io/router-for-me/cliproxyapi. Each service has CPU / memory limits (agent-service is the heaviest).

Routing

nginx (deploy/nginx.conf) is deliberately thin: a single location / proxies everything to gateway, with proxy_buffering off and proxy_read_timeout 3600s so SSE / long streams aren’t cut off. All path routing lives in the gateway (routing.constants.ts), which is health-aware:

  • /v1 and /v1/*relay (RELAY_SERVICE_URL)
  • /api/admin/keeper and /api/admin/keeper/*keeper (KEEPER_SERVICE_URL)
  • /api/(chat|graph|agent|pdf|translate|interview)agent-service, upgrading WebSocket for /api/interview/realtime/:sessionId
  • everything else under /api/ (incl. /api/docs) → platform-api

relay and keeper are not hard depends_on of the gateway. The gateway is health-aware and restart: always, so a late or absent relay/keeper never blocks the core edge — those routes just report unavailable until the service is up.

cliproxy (the account pool)

cliproxy is the CLIProxyAPI reverse proxy over Codex/Claude/Gemini accounts. It is internal-only (no published ports), reached over magic-network:

  • relay forwards /v1 traffic to RELAY_UPSTREAM_BASE_URL (default http://cliproxy:8317/v1).
  • keeper and platform-api reach its management API at CLIPROXY_MANAGEMENT_BASE_URL (default http://cliproxy:8317).

OAuth tokens are logged in manually once and persist in the cliproxy_auth volume; its secret-free config template is rendered to runtime/ before start.

Migration ownership (single writer)

Only platform-api runs prisma migrate deploy at start:prod. agent-service, relay, and keeper deliberately do not run migrations — all apps share one Postgres, and multiple writers would race at boot.

When self-hosting, never let a second app run migrations. In compose the other apps wait for platform-api to start and the infrastructure services to be healthy.

Environment (single .env, interpolated)

Production is driven by one .env in the compose directory. Compose interpolates it into each service’s inline environment: block — there are no per-service env_files. Secrets are declared required with ${VAR:?…}, so a missing value fails the run fast:

  • POSTGRES_PASSWORD, REDIS_PASSWORD — datastore credentials
  • CLERK_SECRET_KEY / CLERK_PUBLISHABLE_KEY / CLERK_AUTHORIZED_PARTIES / CLERK_WEBHOOK_SECRET — auth
  • ENCRYPTION_SECRET_KEY — 64-hex field encryption key
  • GATEWAY_INTERNAL_TOKEN — gateway ↔ backend trust
  • CORS_ORIGINS, MAGIC_CORE_IMAGE_TAG (defaults main), and the cliproxy/relay upstream URLs

The deploy/env.*.example files are reference templates for the variable set. For the full list see Configuration.

CI: affected-image builds

.github/workflows/deploy.yml runs all tests first, then uses dorny/paths-filter to decide which images to rebuild:

ChangeRebuilds
apps/platform-api/**platform-api
apps/agent-service/**agent-service
apps/gateway/**gateway
apps/relay/**relay
apps/keeper/**keeper
packages/** · lockfile · workspaceall five app images (they consume @magic/*)
docs / compose / deploy scriptsno image rebuild

Images build as a matrix job, skipping unchanged apps, and are pushed to GHCR.

Last updated on