services: postgres: image: postgres:18-alpine container_name: sol-postgres environment: POSTGRES_USER: sol # Dev default so `docker compose up` works out of the box; override POSTGRES_PASSWORD for # anything that leaves this machine. POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-sol_dev_password} POSTGRES_DB: sol # Loopback-only: the API reaches postgres over the compose network, so publishing the port # is a dev convenience (psql from the host), never a service boundary. ports: - "127.0.0.1:5432:5432" # postgres:18+ expects a single mount at /var/lib/postgresql (data lands in a # subdirectory) so that pg_upgrade --link works without mount-boundary issues. volumes: - sol-pgdata:/var/lib/postgresql healthcheck: test: ["CMD-SHELL", "pg_isready -U sol -d sol"] interval: 5s timeout: 3s retries: 10 # Host ports are offset from the defaults: other long-running projects on this # machine already bind 6379 / 5672 / 15672. Container-internal ports are unchanged. redis: image: redis:8-alpine container_name: sol-redis command: ["redis-server", "--appendonly", "yes"] # Loopback-only and deliberately passwordless: it holds cache/presence data, and every # real consumer lives on the compose network. ports: - "127.0.0.1:6380:6379" volumes: - sol-redisdata:/data healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 5s timeout: 3s retries: 10 rabbitmq: image: rabbitmq:4-management-alpine container_name: sol-rabbitmq environment: RABBITMQ_DEFAULT_USER: ${RABBITMQ_USER:-sol} RABBITMQ_DEFAULT_PASS: ${RABBITMQ_PASSWORD:-sol_dev_password} ports: - "127.0.0.1:5673:5672" - "127.0.0.1:15673:15672" volumes: - sol-rabbitdata:/var/lib/rabbitmq healthcheck: test: ["rabbitmq-diagnostics", "-q", "ping"] interval: 10s timeout: 5s retries: 10 # Official OpenSandbox control plane. It owns Docker access and creates an isolated, # short-lived container for every approved Skill execution. opensandbox: image: opensandbox/server:latest container_name: sol-opensandbox # Loopback-only on the host side: the API reaches it over the compose network. This # control plane holds docker.sock, so a published port would hand anyone on the LAN a # host-root code-execution endpoint gated only by a committed key. ports: - "127.0.0.1:8090:8090" volumes: - /var/run/docker.sock:/var/run/docker.sock - sol-opensandbox-data:/root/.opensandbox - ./opensandbox/config.toml:/etc/opensandbox/config.toml:ro environment: SANDBOX_CONFIG_PATH: /etc/opensandbox/config.toml # Single source of truth for the sandbox key (config.toml defers to it). The default is # committed for dev quickstart — override OPENSANDBOX_API_KEY on any real deployment. OPENSANDBOX_SERVER_API_KEY: ${OPENSANDBOX_API_KEY:-sol-dev-opensandbox-key} healthcheck: test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8090/health')"] interval: 10s timeout: 5s retries: 5 restart: unless-stopped # Native AOT build of Sol.Api (see src/Sol.Api/Dockerfile for the SDK/toolchain notes). # Containerized so it can reach the OpenSandbox control plane over the Compose network. api: # Override to run a CI-built image instead of building locally, e.g. # SOL_API_IMAGE=ghcr.io/aidotnet/sol-api:latest docker compose up image: hejiale010426/sol-api build: context: . dockerfile: src/Sol.Api/Dockerfile container_name: sol-api depends_on: postgres: condition: service_healthy redis: condition: service_healthy rabbitmq: condition: service_healthy ports: # Loopback-only: the web container proxies /api/* to api:8080 over the compose network, # so direct API access is a host-side dev convenience. - "127.0.0.1:5298:8080" environment: ASPNETCORE_ENVIRONMENT: Development ASPNETCORE_URLS: http://+:8080 Postgres__ConnectionString: "Host=postgres;Port=5432;Database=sol;Username=sol;Password=${POSTGRES_PASSWORD:-sol_dev_password}" Postgres__RunMigrationsOnStartup: "true" Redis__Configuration: "redis:6379" Redis__InstanceName: "sol:" RabbitMq__HostName: rabbitmq RabbitMq__Port: "5672" RabbitMq__UserName: ${RABBITMQ_USER:-sol} RabbitMq__Password: ${RABBITMQ_PASSWORD:-sol_dev_password} Realtime__Backplane: "None" Authentication__PublicOrigin: "${PUBLIC_ORIGIN:-http://localhost:3000}" Authentication__GitHub__Enabled: "${GITHUB_OAUTH_ENABLED:-false}" Authentication__GitHub__ClientId: "${GITHUB_OAUTH_CLIENT_ID:-}" Authentication__GitHub__ClientSecret: "${GITHUB_OAUTH_CLIENT_SECRET:-}" Authentication__GitHub__Scope: "read:user user:email" # Dev-only placeholders, overridable — never reuse in production. The image does not ship # appsettings.Development.json, so these env vars are the only key material the container # sees, and Ai__AllowPrivateNetworks stays at the safe default (false): loopback/private # upstreams are a local `dotnet run` convenience, not something containers should allow. DeviceIdentity__Pepper: "${SOL_PEPPER:-dev-only-pepper-do-not-use-in-production}" Ai__EncryptionKey: "${SOL_AI_ENCRYPTION_KEY:-c29sLWRldi1rZXktbm90LWZvci1wcm9kdWN0aW9uISE=}" Skills__SandboxEnabled: "true" Skills__OpenSandboxDomain: "opensandbox:8090" Skills__OpenSandboxApiKey: "${OPENSANDBOX_API_KEY:-sol-dev-opensandbox-key}" Skills__OpenSandboxImage: "opensandbox/code-interpreter:v1.1.0" restart: unless-stopped web: image: ghcr.io/aidotnet/sol-web:latest build: context: ./web args: NEXT_PUBLIC_SITE_URL: https://sol.routin.ai/ API_BASE_URL: http://api:8080 SOL_API_ORIGIN: http://api:8080 container_name: sol-web depends_on: - api ports: - "3000:3000" environment: # Rewrites in next.config.ts and server-side fetches both need the in-network API address. SOL_API_ORIGIN: http://api:8080 API_BASE_URL: http://api:8080 restart: unless-stopped volumes: sol-pgdata: sol-redisdata: sol-rabbitdata: sol-opensandbox-data: