Fly.io provides a fast path to a production deployment with managed Postgres, global edge networking, and simple CLI-based management. This guide walks through deploying the Prebid Sales Agent as a Fly.io application.
fly auth login)Initialize a new Fly application:
fly launch --name adcp-sales --no-deploy
This creates a fly.toml configuration file. Edit it to set the correct internal port and health check:
app = "adcp-sales"
primary_region = "iad"
[build]
image = "your-registry/adcp-sales-agent:latest"
[env]
ENVIRONMENT = "production"
PRODUCTION = "true"
ADCP_SALES_PORT = "8080"
ADCP_SALES_HOST = "0.0.0.0"
ADCP_MULTI_TENANT = "false"
ADCP_AUTH_TEST_MODE = "false"
SKIP_NGINX = "true"
CREATE_DEMO_TENANT = "true"
[http_service]
internal_port = 8080
force_https = true
auto_stop_machines = false
auto_start_machines = true
min_machines_running = 1
[[http_service.checks]]
interval = "10s"
timeout = "5s"
grace_period = "30s"
method = "GET"
path = "/health"
SKIP_NGINX=true on Fly.io because Fly's built-in proxy handles TLS termination and routing. The internal nginx is not needed.
Create a Fly Postgres cluster and attach it to your app:
# Create a Postgres cluster
fly postgres create --name adcp-sales-db --region iad --initial-cluster-size 1 --vm-size shared-cpu-1x --volume-size 10
# Attach it to your app (sets DATABASE_URL automatically)
fly postgres attach adcp-sales-db --app adcp-sales
The fly postgres attach command automatically sets the DATABASE_URL secret on your app. Verify it:
fly secrets list --app adcp-sales
DATABASE_URL set by Fly uses the postgres:// scheme. The Sales Agent expects postgresql+asyncpg://. You may need to override it -- see Step 3.
Set the required secrets and environment variables:
# Override DATABASE_URL with the asyncpg scheme
fly secrets set DATABASE_URL="postgresql+asyncpg://user:password@adcp-sales-db.flycast:5432/adcp_sales" --app adcp-sales
# Generate and set the encryption key
fly secrets set ENCRYPTION_KEY="$(python -c 'from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())')" --app adcp-sales
# Optional: Google OAuth for Admin UI
fly secrets set GAM_OAUTH_CLIENT_ID="your-client-id" --app adcp-sales
fly secrets set GAM_OAUTH_CLIENT_SECRET="your-client-secret" --app adcp-sales
# Optional: Gemini API key (can also be set per-tenant in Admin UI)
fly secrets set GEMINI_API_KEY="your-gemini-key" --app adcp-sales
Deploy the application:
fly deploy --app adcp-sales
Fly.io builds (or pulls) the Docker image, runs the container, and waits for the health check to pass before routing traffic.
Confirm the deployment is healthy:
# Check app status
fly status --app adcp-sales
# View recent logs
fly logs --app adcp-sales
# Test the health endpoint
curl https://adcp-sales.fly.dev/health
Access the Admin UI at https://adcp-sales.fly.dev/admin.
Add a custom domain to your Fly app:
# Add a TLS certificate for your domain
fly certs add adcp.yourcompany.com --app adcp-sales
Then create a DNS CNAME record:
Type: CNAME
Name: adcp.yourcompany.com
Value: adcp-sales.fly.dev
TTL: 300
Verify the certificate:
fly certs show adcp.yourcompany.com --app adcp-sales
Increase the machine size:
fly scale vm shared-cpu-2x --app adcp-sales
fly scale memory 1024 --app adcp-sales
Run multiple instances:
fly scale count 2 --app adcp-sales
The application is stateless, so multiple instances can serve traffic concurrently with the shared Postgres database.
Scale the Postgres cluster:
fly postgres config update --app adcp-sales-db --max-connections 100
To run in multi-tenant mode on Fly.io:
fly secrets set ADCP_MULTI_TENANT="true" --app adcp-sales
fly secrets set BASE_DOMAIN="adcp.yourplatform.com" --app adcp-sales
fly secrets set SALES_AGENT_DOMAIN="adcp.yourplatform.com" --app adcp-sales
fly secrets set SUPER_ADMIN_EMAILS="admin@yourplatform.com" --app adcp-sales
fly certs add "*.adcp.yourplatform.com" --app adcp-sales
Create a wildcard CNAME record:
Type: CNAME
Name: *.adcp.yourplatform.com
Value: adcp-sales.fly.dev
TTL: 300
For tenants with custom domains, add individual certificates:
fly certs add ads.acmenews.com --app adcp-sales
The tenant must create a CNAME from ads.acmenews.com to adcp-sales.fly.dev.
View real-time logs:
fly logs --app adcp-sales
fly status --app adcp-sales
fly dashboard --app adcp-sales
Fly.io automatically monitors the /health endpoint defined in fly.toml. If health checks fail, Fly restarts the machine and alerts you through the dashboard.
To enable structured logging with Logfire, set the token:
fly secrets set LOGFIRE_TOKEN="your-logfire-token" --app adcp-sales
| Component | Specification | Estimated Monthly Cost |
|---|---|---|
| Fly Machine | shared-cpu-1x, 512 MB | ~$5 |
| Fly Machine | shared-cpu-2x, 1 GB | ~$12 |
| Fly Postgres | shared-cpu-1x, 10 GB disk | ~$7 |
| Custom domain + TLS | Included | $0 |
| Bandwidth | 100 GB included | $0 (typical usage) |
| Total (minimal) | ~$12/mo | |
| Total (recommended) | ~$19/mo |
iad (Virginia) region.
fly.toml (grace_period = "60s")fly logs --app adcp-salesDATABASE_URL points to the correct Fly Postgres instancefly postgres connect --app adcp-sales-dbDATABASE_URL uses the .flycast internal addresspostgresql+asyncpg://dig adcp.yourcompany.com CNAMEfly certs show adcp.yourcompany.com --app adcp-sales