Deployment & Development
1. Prerequisites
| Tool | Minimum Version | Notes |
|---|---|---|
| Docker | 24+ | Desktop or Engine |
| Docker Compose | v2 | Bundled with Docker Desktop; verify with docker compose version |
| OpenAI API key or Azure OpenAI credentials | – | At least one LLM provider must be configured |
| Git | 2.x | For cloning the repo |
Optional for local (non-Docker) development:
| Tool | Version | Notes |
|---|---|---|
| Python | 3.12 | Required only for running agents outside Docker |
| uv | latest | Python package manager (pip install uv or brew install uv) |
| Node.js | 22+ | Required only for running the frontend outside Docker |
| pnpm | 9+ | Enable via corepack enable && corepack prepare pnpm@latest --activate |
2. Docker Compose Architecture
The platform runs 11 services organized into 4 profile groups. Services without a profile start by default.
graph TB
accTitle: 2. Docker Compose Architecture
subgraph defaultGroup["Default (always start)"]
style defaultGroup fill:#f8fafc,stroke:#e2e8f0,stroke-width:2px
db["PostgreSQL 16 + pgvector<br/>:5432"]
redis["Redis 7<br/>:6379"]
aspire["Aspire Dashboard<br/>:18888"]
end
subgraph seed["Profile: seed"]
style seed fill:#f8fafc,stroke:#e2e8f0,stroke-width:2px
seeder["Seeder<br/>(run once, exits)"]
end
subgraph agents["Profile: agents"]
style agents fill:#f8fafc,stroke:#e2e8f0,stroke-width:2px
orchestrator["Orchestrator<br/>:8080"]
product["Product Discovery<br/>:8081"]
order["Order Management<br/>:8082"]
pricing["Pricing & Promotions<br/>:8083"]
review["Review & Sentiment<br/>:8084"]
inventory["Inventory & Fulfillment<br/>:8085"]
end
subgraph frontend_profile["Profile: frontend"]
style frontend_profile fill:#f8fafc,stroke:#e2e8f0,stroke-width:2px
frontend["Next.js Frontend<br/>:3000"]
end
seeder -->|depends_on| db
orchestrator -->|depends_on| db
orchestrator -->|depends_on| redis
orchestrator -->|depends_on| aspire
product -->|depends_on| db
product -->|depends_on| aspire
order -->|depends_on| db
order -->|depends_on| aspire
pricing -->|depends_on| db
pricing -->|depends_on| aspire
review -->|depends_on| db
review -->|depends_on| aspire
inventory -->|depends_on| db
inventory -->|depends_on| aspire
frontend -->|depends_on| orchestrator
orchestrator -- "A2A Protocol" --> product
orchestrator -- "A2A Protocol" --> order
orchestrator -- "A2A Protocol" --> pricing
orchestrator -- "A2A Protocol" --> review
orchestrator -- "A2A Protocol" --> inventory
3. Service Profiles
Docker Compose profiles control which services start. Services without a profile always start.
| Profile | Services | When to Use |
|---|---|---|
| (none) | db, redis, aspire | Always started – infrastructure baseline |
seed | seeder | Populates the database with sample data. Runs once and exits. |
agents | orchestrator, product-discovery, order-management, pricing-promotions, review-sentiment, inventory-fulfillment | The 6 AI agent microservices |
frontend | frontend | Next.js web application |
Usage examples:
# Infrastructure only
docker compose up -d
# Infrastructure + agents
docker compose --profile agents up -d
# Everything
docker compose --profile agents --profile frontend up -d
# Run the seeder
docker compose --profile seed run --rm seeder
4. dev.sh Script
The scripts/dev.sh script is the recommended way to start the development environment. It handles build ordering, health checks, seeding, and prints a summary when ready.
On Windows, use scripts/dev.ps1 — a PowerShell script with identical behaviour: same profiles, same ordering, same health gates, and the same flags in PowerShell form (--clean → -Clean, --seed-only → -SeedOnly, --infra-only → -InfraOnly, --dotnet → -Dotnet). It also runs on macOS and Linux under PowerShell 7, though dev.sh is the more idiomatic choice there. Everything documented in this section applies to both.
Flags
| Flag | Description |
|---|---|
| (no flags) | Full rebuild: stops existing containers, builds all images, starts infrastructure, seeds the database, starts all agents, starts the frontend |
--clean | Nuclear option: removes all containers, volumes (including DB data), and orphans, then does a full rebuild |
--seed-only | Ensures infrastructure is running, then re-runs the seeder against the existing database. Useful after schema changes or to reset sample data. |
--infra-only | Starts only db, redis, and aspire. Does not start agents or frontend. Use this when running agents locally via uvicorn. |
--help, -h | Prints usage information |
Script Flow
flowchart TD
accTitle: Script Flow
start([dev.sh]) --> check_prereqs{Docker +<br/>Compose installed?}
check_prereqs -- No --> fail_exit([Exit with error])
check_prereqs -- Yes --> check_env{.env exists?}
check_env -- No --> copy_env[Copy .env.example to .env]
check_env -- Yes --> parse_flags
copy_env --> parse_flags
parse_flags --> is_clean{--clean?}
is_clean -- Yes --> clean[docker compose down -v<br/>Remove volumes + orphans]
is_clean -- No --> is_seed_only
clean --> is_seed_only{--seed-only?}
is_seed_only -- Yes --> start_infra_seed[Start db + redis + aspire]
start_infra_seed --> health_infra_seed[Wait for health checks]
health_infra_seed --> run_seeder_only[Run seeder]
run_seeder_only --> exit_seed([Exit])
is_seed_only -- No --> stop_existing[Stop existing containers]
stop_existing --> build[Build agent images]
build --> start_infra[Start db + redis + aspire]
start_infra --> health_infra[Wait for health checks]
health_infra --> run_seeder[Run database seeder]
run_seeder --> is_infra_only{--infra-only?}
is_infra_only -- Yes --> summary_infra[Print infrastructure summary]
summary_infra --> exit_infra([Exit])
is_infra_only -- No --> start_agents[Start all 6 agents]
start_agents --> health_agents[Wait for agent /health endpoints]
health_agents --> start_frontend[Start frontend]
start_frontend --> health_frontend[Wait for frontend :3000]
health_frontend --> summary_full[Print full summary]
summary_full --> done([Done])
5. Environment Configuration
Copy .env.example to .env and configure:
cp .env.example .env
LLM Provider
| Variable | Required | Default | Description |
|---|---|---|---|
LLM_PROVIDER | Yes | openai | LLM provider: openai, azure, or replay (plays back recorded fixtures, no credentials — see shared/replay_client.py) |
OpenAI Configuration
| Variable | Required | Default | Description |
|---|---|---|---|
OPENAI_API_KEY | Yes (if openai) | – | Your OpenAI API key. Any non-empty string works against a local server that doesn’t check it (Ollama, LM Studio) |
LLM_MODEL | No | gpt-4.1 | Chat completion model name |
LLM_BASE_URL | No | unset (uses api.openai.com) | Only takes effect when LLM_PROVIDER=openai. Points OpenAIChatClient at any OpenAI-compatible endpoint instead — GitHub Models, OpenRouter, vLLM, LM Studio, or a local Ollama server (http://localhost:11434/v1). See tutorials/00-setup/README.md for worked examples and a tool-calling-support gotcha before picking a local model. |
Azure OpenAI Configuration
| Variable | Required | Default | Description |
|---|---|---|---|
AZURE_OPENAI_ENDPOINT | Yes (if azure) | – | Azure OpenAI resource endpoint URL |
AZURE_OPENAI_KEY | Yes (if azure) | – | Azure OpenAI API key |
AZURE_OPENAI_DEPLOYMENT | Yes (if azure) | – | Deployment name for chat completions |
AZURE_OPENAI_API_VERSION | No | 2024-12-01-preview | Azure OpenAI API version |
Embeddings
| Variable | Required | Default | Description |
|---|---|---|---|
EMBEDDING_MODEL | No | text-embedding-3-small | OpenAI embedding model for product semantic search (pgvector) |
AZURE_EMBEDDING_DEPLOYMENT | No (if azure) | – | Azure OpenAI deployment for embeddings |
Database
| Variable | Required | Default | Description |
|---|---|---|---|
POSTGRES_DB | No | ecommerce_agents | PostgreSQL database name |
POSTGRES_USER | No | ecommerce | PostgreSQL user |
POSTGRES_PASSWORD | No | ecommerce_secret | PostgreSQL password |
DATABASE_URL | No | postgresql://ecommerce:ecommerce_secret@db:5432/ecommerce_agents | Full connection string. In Docker, db resolves to the Compose service. For local dev, use localhost. |
Redis
| Variable | Required | Default | Description |
|---|---|---|---|
REDIS_URL | No | redis://redis:6379 | Redis connection string. In Docker, redis resolves to the Compose service. |
Auth
| Variable | Required | Default | Description |
|---|---|---|---|
JWT_SECRET | Yes | change-me-... | Secret key for signing JWTs. Generate with: python -c "import secrets; print(secrets.token_hex(32))" |
AGENT_SHARED_SECRET | No | agent-internal-shared-secret | Shared secret for inter-agent authentication (orchestrator to specialists) |
Telemetry
| Variable | Required | Default | Description |
|---|---|---|---|
OTEL_ENABLED | No | true | Enable/disable OpenTelemetry export |
OTEL_EXPORTER_OTLP_ENDPOINT | No | http://aspire:18889 | OTLP receiver endpoint (Aspire Dashboard) |
OTEL_SERVICE_NAME | No | ecommerce.orchestrator | Service name reported to OTLP. Each agent overrides this in docker-compose.yml. |
General
| Variable | Required | Default | Description |
|---|---|---|---|
ENVIRONMENT | No | development | Runtime environment identifier |
AGENT_REGISTRY | No | (JSON map) | JSON object mapping agent names to their internal Docker network URLs. Used by the orchestrator to discover specialist agents. |
NEXT_PUBLIC_API_URL | No | http://localhost:8080 | Orchestrator URL as seen by the browser. The frontend uses this for API calls. |
6. Dockerfile Architecture
Agent Dockerfile (Multi-Target)
All 6 agents share a single agents/Dockerfile. The build target is controlled via two ARG values:
| ARG | Default | Purpose |
|---|---|---|
AGENT_NAME | orchestrator | Python package directory to copy (e.g., product_discovery, order_management) |
AGENT_PORT | 8080 | Port the agent listens on |
Build flow:
- Base image:
python:3.12-slimwith system dependencies (gcc,libpq-dev,curl) - Install uv: Copied from the official
ghcr.io/astral-sh/uvimage - Create non-root user:
agentuser and group - Install Python deps:
uv sync --no-dev --no-install-project(cached layer – only re-runs whenpyproject.tomlchanges) - Copy shared library:
shared/directory used by all agents - Copy agent module: Only the
${AGENT_NAME}/directory for this specific agent - Switch to non-root user
- Health check:
curl -f http://localhost:${AGENT_PORT}/health - Entrypoint:
uv run uvicorn ${AGENT_NAME}.main:app --host 0.0.0.0 --port ${AGENT_PORT}
The seeder service reuses the orchestrator image but overrides the command to run uv run python -m scripts.seed with the scripts/ directory mounted as a read-only volume.
Frontend Dockerfile (Multi-Stage)
The web/Dockerfile uses a 3-stage build for minimal production images:
| Stage | Base | Purpose |
|---|---|---|
deps | node:22-alpine | Install dependencies with pnpm install --frozen-lockfile |
builder | node:22-alpine | Build Next.js (pnpm build), inlines NEXT_PUBLIC_API_URL at build time |
runner | node:22-alpine | Production runtime with standalone output only. Non-root nextjs user. |
The final image contains only the standalone server, static assets, and public directory – no node_modules or source code.
7. Local Development
For faster iteration, run infrastructure in Docker and agents/frontend locally.
Start infrastructure:
./scripts/dev.sh --infra-only
Run a single agent:
cd agents
export DATABASE_URL=postgresql://ecommerce:ecommerce_secret@localhost:5432/ecommerce_agents
export REDIS_URL=redis://localhost:6379
export OPENAI_API_KEY=sk-your-key
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:18890
uv run uvicorn product_discovery.main:app --port 8081 --reload
Run the orchestrator (needs AGENT_REGISTRY pointing to local ports):
cd agents
export AGENT_REGISTRY='{"product-discovery":"http://localhost:8081","order-management":"http://localhost:8082","pricing-promotions":"http://localhost:8083","review-sentiment":"http://localhost:8084","inventory-fulfillment":"http://localhost:8085"}'
uv run uvicorn orchestrator.main:app --port 8080 --reload
Run the frontend:
cd web
pnpm install
pnpm dev
The frontend starts on http://localhost:3000 and expects the orchestrator at http://localhost:8080 (configurable via NEXT_PUBLIC_API_URL).
Seed the database locally:
cd agents
DATABASE_URL=postgresql://ecommerce:ecommerce_secret@localhost:5432/ecommerce_agents \
uv run python -m scripts.seed
Generate embeddings locally:
cd agents
DATABASE_URL=postgresql://ecommerce:ecommerce_secret@localhost:5432/ecommerce_agents \
OPENAI_API_KEY=sk-your-key \
uv run python -m scripts.generate_embeddings
8. Port Map
| Port | Service | Protocol | Notes |
|---|---|---|---|
| 3000 | Next.js Frontend | HTTP | Browser-facing UI |
| 5432 | PostgreSQL | TCP | pgvector enabled |
| 6379 | Redis | TCP | Session cache (rate limiting is not implemented yet — planned) |
| 8080 | Orchestrator (Customer Support Agent) | HTTP | API gateway – all user requests enter here |
| 8081 | Product Discovery Agent | HTTP | A2A endpoint, called by orchestrator |
| 8082 | Order Management Agent | HTTP | A2A endpoint, called by orchestrator |
| 8083 | Pricing & Promotions Agent | HTTP | A2A endpoint, called by orchestrator |
| 8084 | Review & Sentiment Agent | HTTP | A2A endpoint, called by orchestrator |
| 8085 | Inventory & Fulfillment Agent | HTTP | A2A endpoint, called by orchestrator |
| 18888 | Aspire Dashboard | HTTP | OpenTelemetry traces and logs UI |
| 18890 | Aspire OTLP Receiver | gRPC | Mapped from container port 18889 |
9. Health Checks
Docker Health Checks
All services have built-in health checks defined in docker-compose.yml or the Dockerfile:
| Service | Check | Interval | Timeout | Retries |
|---|---|---|---|---|
| PostgreSQL | pg_isready -U ecommerce | 5s | 3s | 5 |
| Redis | redis-cli ping | 5s | 3s | 5 |
| All agents | curl -f http://localhost:{PORT}/health | 15s | 5s | 3 (30s start period) |
Manual Verification
# Check all container statuses
docker compose --profile agents --profile frontend ps
# Check individual agent health
curl http://localhost:8080/health # Orchestrator
curl http://localhost:8081/health # Product Discovery
curl http://localhost:8082/health # Order Management
curl http://localhost:8083/health # Pricing & Promotions
curl http://localhost:8084/health # Review & Sentiment
curl http://localhost:8085/health # Inventory & Fulfillment
# Check PostgreSQL connectivity
docker compose exec db pg_isready -U ecommerce
# Check Redis
docker compose exec redis redis-cli ping
# View OpenTelemetry traces
open http://localhost:18888
10. Troubleshooting
For common issues — port conflicts, missing API keys, empty data, Aspire traces, build failures, and frontend errors — see troubleshooting.md.
Deployment-specific issues are below.
Database volume out of date after schema changes
Cause: init.sql only runs on the first volume creation. Modifying the schema after the volume exists has no effect.
Fix: Destroy the volume and re-initialize:
./scripts/dev.sh --clean
This removes the pgdata volume, re-creates the database from init.sql, and re-seeds.
Seeder fails with “relation does not exist”
Cause: Same root cause — the database volume was created before the latest init.sql.
Fix: ./scripts/dev.sh --clean.
Docker build slow — dependency layer invalidated
Cause: Changing pyproject.toml triggers a full uv sync reinstall.
Fix: The Dockerfile is structured so the dependency layer is independent of source code. If you only changed .py files, uv sync is reused from cache. Avoid touching pyproject.toml unless you are adding or removing dependencies.
“Permission denied” running dev.sh
chmod +x scripts/dev.sh
Related
docs/troubleshooting.md— runtime issues (port conflicts, LLM errors, DB connection, Aspire traces)docs/architecture.md— system overview and agent patterns- Project README
Source: docs/deployment.md — this page is generated from the repository.