The Prebid Sales Agent can be deployed on any platform that supports Docker containers and PostgreSQL. This guide compares the available deployment options and outlines the prerequisites common to all platforms.
| Criteria | Docker (Local / On-Prem) | Fly.io | Google Cloud Run |
|---|---|---|---|
| Setup Time | ~2 minutes | ~10 minutes | ~20 minutes |
| Difficulty | Low | Medium | Medium–High |
| Database | Docker PostgreSQL 17 | Fly Postgres (managed) | Cloud SQL for PostgreSQL |
| Scaling | Manual (docker compose scale) | fly scale CLI |
Auto-scaling (min/max instances) |
| Custom Domains | nginx + Let’s Encrypt | fly certs add |
Cloud Run domain mappings |
| Multi-Tenant | Supported | Supported (wildcard certs) | Supported (load balancer) |
| Estimated Cost | Infrastructure only | ~$5–15/mo (shared CPU) | Pay-per-request + Cloud SQL |
| Best For | Development, small publishers, on-prem | Small–medium production | Enterprise, Google ecosystem |
For platform-specific instructions see:
The Sales Agent supports two deployment modes that determine how tenants are routed.
In single-tenant mode the application serves a single publisher. All requests are routed by path, and no subdomain resolution is needed. This is the default when ADCP_MULTI_TENANT is unset or false.
/mcp, /a2a, /admin, /api/v1)When ADCP_MULTI_TENANT=true, the application resolves tenants from the request subdomain. Each tenant receives its own subdomain under BASE_DOMAIN (e.g., acme.yourdomain.com). Tenants can also configure a virtual_host for fully custom domains.
tenant.BASE_DOMAIN)See Multi-Tenant Deployment for full configuration details.
| Component | Minimum | Recommended | Notes |
|---|---|---|---|
| PostgreSQL | 12+ | 17 (latest) | Used for all persistent state; 17-alpine ships in docker-compose |
| Docker | 20.10+ | Latest stable | Required for containerized deployment |
| RAM | 1 GB | 2 GB | Includes FastAPI, Flask Admin UI, nginx, and cron |
| CPU | 1 vCPU | 2 vCPU | More cores help under concurrent MCP/A2A traffic |
| Disk | 1 GB | 5 GB | Database storage grows with audit logs and media buys |
| Network | HTTP/HTTPS | HTTPS with TLS 1.2+ | nginx handles TLS termination in Docker deployments |
Regardless of which platform you choose, you will need the following before you begin.
The Sales Agent ships as a multi-stage Docker image. The build process uses uv as the Python package manager in a builder stage, then copies the virtual environment into a slim runtime image that includes Python, nginx, and supercronic for cron jobs.
docker build -t adcp-sales-agent .
All deployment methods require a PostgreSQL 12+ database. The Sales Agent runs Alembic migrations automatically on startup via the db-init service (or directly when SKIP_MIGRATIONS=false).
At a minimum every deployment needs these variables set:
| Variable | Description |
|---|---|
DATABASE_URL |
PostgreSQL connection string (postgresql+asyncpg://user:pass@host/db) |
ENVIRONMENT |
development or production |
ENCRYPTION_KEY |
Fernet key for encrypting sensitive fields (auto-generated if absent) |
See the Security Model page for the full list of security-related variables.
After any deployment, verify the application is running:
curl http://localhost:8080/health
A successful response confirms the FastAPI application is up and the database connection is healthy.
The Sales Agent exposes several internal ports. Your deployment platform must route external traffic to the appropriate port.
| Port | Service | Description |
|---|---|---|
| 8000 | nginx reverse proxy | External entry point; proxies to internal services |
| 8080 | FastAPI unified app | All protocols: MCP, A2A, REST API, Admin UI |
As of v1.5.0, the application uses a single unified process on port 8080. Ports 8001 (Admin UI) and 8091 (A2A) from the legacy multi-process architecture are no longer used.
In most deployments nginx on port 8000 is the only port exposed externally.
The container entrypoint runs a Python orchestrator that starts all services:
/app/.venv/bin/python scripts/deploy/run_all_services.py
This script starts:
SKIP_MIGRATIONS=true)SKIP_NGINX=true)SKIP_CRON=true)