docs: add comprehensive AWS ECS Fargate deployment guide
From the PR description
Summary
- Adds
docs/aws-deployment.md- a full end-to-end production deployment guide for Mike on AWS - Covers every AWS service required: VPC/networking, Cognito+SES, RDS Postgres, S3, Bedrock, ACM, Route 53, Secrets Manager, IAM, ECS Fargate, and ALB
- Includes app-specific gotchas not obvious from the existing README (SSE streaming idle-timeout,
TRUST_PROXY_HOPS=2behind ALB,NEXT_PUBLIC_*build-time baking requirement, unnecessary SES task-role grant)
What's included
- Architecture diagram and per-service resource sizing rationale
- Step-by-step AWS CLI commands for every resource (VPC, subnets, SGs, Cognito User Pool + app client, RDS with force-TLS, S3 with CORS + versioning, Bedrock model access, Secrets Manager, scoped IAM task/execution roles, ECS cluster + log groups, ALBs with correct idle timeouts, ECS task definitions and services, autoscaling)
- Database migration pattern using
ecs run-taskwith a command override - Day-2 operations: image deploys, secret rotation, scaling, backups
- Observability: CloudWatch alarms, Container Insights, cost estimate
- Security checklist (pre-launch + post-launch)
- Troubleshooting section for the most common failure modes
- Appendix A: optional improvements (ECR mirror, WAFv2, blue/green, Multi-AZ)
- Appendix B: private GHCR image credential wiring
Test plan
- Follow §16 Smoke Test end-to-end on a fresh AWS account
- Verify all AWS CLI commands parse without error (
--dry-runwhere supported) - Confirm Bedrock model ARNs match current model ids in
backend/src/lib/llm/claude.ts
🤖 Generated with Claude Code
Our analysis
Re-platform from Cloudflare/Supabase to AWS — read the full analysis →
Frontend lint cleanups surfaced by new CI — read the full analysis →
Think the analysis missed something the PR description covers?
Commits in this PR (2)
| SHA | Subject | Author | Date | |
|---|---|---|---|---|
2dc27676 | Re-platform from Cloudflare/Supabase to AWS | Alex Maingot | 2026-05-14 | ↗ GitHub |
commit bodyAuth: Supabase Auth → AWS Cognito. Frontend uses amazon-cognito-identity-js
behind a supabase-shaped wrapper so existing call sites only changed import
paths. Backend uses aws-jwt-verify (CognitoJwtVerifier for real AWS,
JwtRsaVerifier with a custom HTTP fetcher for cognito-local). Signup page
now handles Cognito's email-confirmation step.
DB: Supabase PostgREST → RDS Postgres via Drizzle ORM. ~191 query sites
across 14 backend files (~8000 lines) rewritten. Drizzle schema mirrors
the original 17 tables minus the auth.uid()-based RLS helpers - access is
already enforced in lib/access.ts via service-role queries (defense in
depth note in the original schema.sql:1052). Initial migration committed
under backend/drizzle/. A new public.users table mirrors Cognito identities
(replaces the Postgres on-signup trigger).
Storage: R2 → S3 envs (S3_BUCKET_NAME, S3_ENDPOINT_URL, AWS_*). Endpoint
and forcePathStyle are conditional - set for MinIO locally, unset for real
AWS so the SDK uses the ECS task-role credential chain.
LLM: Anthropic SDK → @aws-sdk/client-bedrock-runtime for Claude only.
OpenAI and Gemini still call their providers directly. Per-user Claude
keys are dropped (Bedrock uses IAM); a migration purges existing rows and
narrows the user_api_keys.provider check constraint.
Account deletion: routes/user.ts now calls AdminDeleteUserCommand on the
Cognito User Pool plus a cascading delete on public.users.
Frontend runtime: Cloudflare Workers / OpenNext → Next.js standalone in
Docker (output: "standalone" + `node server.js`). Removes @opennextjs/
cloudflare, wrangler, @openrouter/sdk, @supabase/*.
Backend runtime: nixpacks → node:20-bookworm-slim with libreoffice +
fontconfig baked in for DOC/DOCX → PDF conversion.
Infra artifacts:
- frontend/Dockerfile, backend/Dockerfile (multi-stage, healthchecked)
- docker-compose.yml: postgres, cognito-local (ghcr.io/amaingot/cognito-local),
MinIO + minio-init for bucket bootstrap, smtp4dev, backend, frontend
- scripts/bootstrap-local.sh: pre-seeds cognito-local's clients.json so the
fork's auto-generated client id doesn't clash with the env-pinned id,
then runs Drizzle migrations
- .github/workflows/ci.yml: PR build + lint + db:migrate against a postgres
service
- .github/workflows/build-and-publish.yml: multi-arch (amd64/arm64) push to
ghcr.io/<owner>/mike-{frontend,backend} on main and v* tags
End-to-end verified locally:
- docker compose up brings postgres/auth/minio/smtp to healthy
- bootstrap-local.sh provisions pool + client + bucket + schema
- Cognito signup → confirm → InitiateAuth → backend JWKS verify → users
row auto-created (with deterministic UUID derivation for cognito-local's
non-UUID subs)
- POST /projects, GET /projects, POST /single-documents all return 200
with the expected DB rows and MinIO object
Operator notes captured in README.md: required AWS resources (Cognito
pool, RDS, S3, SES, Bedrock model access, ECS task role permissions),
ghcr.io → ECR re-tag flow, Bedrock model-ID verification step before
first deploy.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
| ||||
e7593aa7 | Fix frontend lint errors surfaced by new CI | Alex Maingot | 2026-05-14 | ↗ GitHub |
commit bodyThe Re-platform commit introduced .github/workflows/ci.yml which runs `npm run lint` for the first time on this codebase. That surfaced 38 pre-existing errors in code inherited from upstream willchen96/mike, which never ran ESLint in CI. Breakdown of fixes: - 16 react-hooks/set-state-in-effect (new in eslint-plugin-react-hooks 7 / Next 16): converted to the React "adjusting state during render" pattern, useSyncExternalStore for localStorage-backed state, or deferred initial measurements via requestAnimationFrame/setTimeout. - 2 react-hooks/refs in ChatView: dropped a no-op .current entry from a dep array that was already covered by an eslint-disable comment. - 1 react-hooks/immutability in DocView: hoisted scrollToHighlightOnPage above its first useCallback caller. - 1 react-hooks/static-components in WFColumnViewModal: switched the dynamic icon to React.createElement so we don't bind a component to a PascalCase local during render. - 11 @typescript-eslint/no-explicit-any: removed redundant casts (MikeMessage already typed files/error/workflow), typed the caret-from-point document shape, used unknown + narrowing in catch. - 5 react/no-unescaped-entities: escaped apostrophes/quotes in JSX text. - 2 @typescript-eslint/no-require-imports: ignored src/scripts/** in eslint config - the one file there is a CJS build-time conversion script, not part of the app bundle. next build still succeeds. Zero behavior changes intended - the React Compiler-aware rules were all addressed by restructuring effects into pure-render derivations rather than disabling them. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> | ||||
Capture this PR into my fork
Download a Markdown prompt that tells Claude how to port every
commit in this PR into your working tree. Run it via
claude -p < capture-pull-153.md from
inside the repo you want the changes in.