Deployment
The reference deployment model is AWS — Fargate for the backend, Amplify / Cloudflare Pages for the frontend. The setup is documented at length in agent-poc/AWS-MIGRATION.md; this page distills the marketplace-specific concerns.
Components
| Tier | Service | Notes |
|---|---|---|
| Marketplace backend | ECS Fargate behind ALB | Migrations + seed run on container start |
| Marketplace DB | RDS Postgres 15 | DATABASE_CERT for TLS, allow Fargate task SG inbound |
| Marketplace frontend | Cloudflare Pages (current) or Amplify | Built from fleapo-marketplace via Wrangler |
| Marketplace secrets | AWS Secrets Manager | Injected as env vars into ECS task definition |
| Marketplace audit | PostHog (managed) | Batch egress over HTTPS |
| Marketplace billing | Stripe (managed) | Webhook back at /webhooks/stripe |
| Agent backends | Each agent's own infra | Sometimes Fargate, sometimes Railway/Vercel |
Marketplace backend (Fargate)
| Concern | Setting |
|---|---|
| Task size | 1 vCPU, 2 GB |
| Desired count | 2 (HA) |
| Auto-scaling | Scale out at CPU > 70%, scale in at < 30% |
| Health check | GET /health → 200 |
| Idle timeout (ALB) | 60s is fine for marketplace (no SSE) |
| Logs | CloudWatch Logs (or stdout to Fargate driver) |
The marketplace doesn't stream SSE today, so the ALB idle timeout doesn't need the 4000s bump that agent-poc does.
Cloudflare Pages (frontend)
wrangler.jsonc is in the repo:
{
"name": "fleapo-marketplace",
"compatibility_date": "...",
"pages_build_output_dir": "dist",
"assets": { "not_found_handling": "single-page-application" },
"observability": { "enabled": true }
}
SPA fallback not_found_handling: "single-page-application" rewrites unknown routes to /index.html so client-side routing works.
Build:
pnpm install
pnpm build
npx wrangler pages deploy dist --project-name fleapo-command-prod
CI/CD (GitHub Actions)
A reasonable pipeline per repo:
| Repo | On push to main |
|---|---|
marketplace-fleapoai-service | Build Docker image with --platform linux/amd64 → push to ECR (tag: :sha + :latest) → render task def → ECS service update |
fleapo-marketplace | pnpm install && pnpm build → wrangler pages deploy dist |
marketplace-docs | pnpm install && pnpm refresh-api && pnpm build → deploy build/ to chosen target |
Use AWS OIDC for ECR + ECS auth, not long-lived access keys.
Secrets management
In production, never put secrets in env literals. Use AWS Secrets Manager:
// task-definition.json (excerpt)
"secrets": [
{ "name": "DATABASE_URL", "valueFrom": "arn:aws:secretsmanager:...:database-url" },
{ "name": "AUTH_SECRET", "valueFrom": "arn:aws:secretsmanager:...:auth-secret" },
{ "name": "STRIPE_SECRET_KEY", "valueFrom": "arn:aws:secretsmanager:...:stripe-secret" },
{ "name": "STRIPE_WEBHOOK_SECRET", "valueFrom": "arn:aws:secretsmanager:...:stripe-webhook" },
{ "name": "POSTHOG_API_KEY", "valueFrom": "arn:aws:secretsmanager:...:posthog-key" }
]
Database
| Setting | Value |
|---|---|
| Engine | RDS Postgres 15 |
| Multi-AZ | Yes (HA) |
| Backups | 7-day point-in-time |
| TLS | Required; cert distributed via DATABASE_CERT env |
| Connection pooling | App-side via pg.Pool; consider RDS Proxy at scale |
DNS
api.fleapo.ai→ ALBmarketplace.fleapo.ai→ Cloudflare Pages (or Amplify)docs.fleapo.ai→ docs site
Configure SPF / DMARC for the email-from address used by the interactions fanout.
Costs (rough)
| Component | $/mo |
|---|---|
| Fargate (2 tasks × 1vCPU × 2GB) | $40 |
| ALB | $25 |
| RDS db.t4g.small Multi-AZ | $50 |
| Secrets Manager | $5 |
| CloudWatch | $5 |
| Cloudflare Pages | free |
| Total | ~$125/mo |
Plus PostHog (free tier covers most projects) and Stripe (per-transaction).
Disaster recovery
| Risk | Mitigation |
|---|---|
| DB corruption / data loss | RDS PITR (point-in-time restore) within 7d |
| Bad deploy | ECS rolling deploy, min 100% / max 200%; revert by re-deploying previous task def |
| Cloudflare outage | Pages re-routes to default origin; frontend gracefully degrades to mock data |
| Stripe outage | Free-tier signups still work; paid checkouts return 503; webhooks queue server-side and retry |