Full AWS re-platform: Cognito auth, Drizzle ORM, S3, Bedrock, Docker compose stack

amaingot lands a 64-file, ~16k-line commit that swaps every Cloudflare/Supabase dependency for AWS: Cognito for auth, RDS Postgres via Drizzle for data, S3 for storage, Bedrock for Claude. A full local dev stack (MinIO, cognito-local, smtp4dev) comes with it via docker-compose. This is the fork's defining change.

infrastructureintegration

The auth swap is the most involved piece. On the frontend, amazon-cognito-identity-js sits behind a Supabase-shaped wrapper so call sites only changed imports. On the backend, aws-jwt-verify validates tokens, with a dual path: real Cognito JWTs in production, cognito-local tokens in the compose stack. Cognito Local issues non-UUID sub identifiers, so the commit derives deterministic UUIDs from them for local dev -- flagged in the commit message as something to revisit if the auth provider changes again.

Database access moves from PostgREST/Supabase client to Drizzle ORM against direct Postgres. Roughly 191 query sites across 14 backend files were rewritten; the initial migration is under backend/drizzle/. There's a note in lib/access.ts explaining why the removal of Supabase RLS is safe in this architecture -- the argument is spelled out at schema.sql:1052 if you want to evaluate it.

The Claude API path goes through @aws-sdk/client-bedrock-runtime instead of the Anthropic SDK. Per-user Claude API keys are dropped entirely -- Bedrock authenticates via IAM so there's no per-user credential. This is a destructive migration: it purges user_api_keys rows and narrows the provider check constraint to exclude Claude from the BYO-key flow. OpenAI and Gemini still support per-user keys. Any fork still offering BYO-key Claude to end users cannot adopt this change as-is.

Deployment moves from Cloudflare Workers/OpenNext to Next.js standalone in Docker, with frontend/Dockerfile, backend/Dockerfile, and a docker-compose.yml that wires Postgres 16, cognito-local, MinIO, and smtp4dev. Two Actions workflows are added: PR-time lint/build/migrate against a postgres service container, and multi-arch GHCR publish on main and v* tags.

So what Worth studying if your fork targets AWS infrastructure. The auth wrapper pattern, the Drizzle migration setup, and the `lib/access.ts` defense-in-depth note are useful even if you only adopt parts. The local dev compose stack is the cleanest self-hostable reference in the forks tracked here. The blocking caveat: dropping per-user Claude keys is a product-level commitment. Don't pull this in unless you are ready to authenticate all Claude calls through Bedrock IAM and drop BYO-key support for end users.

View this fork on GitHub →

Spotted something wrong? Or know the PR text has fresher detail than the writeup above?

Commits in this thread

1 commit from amaingot/mike-aws, oldest first. Source extracted verbatim from the harvested git log.

SHA Subject Author Date
2dc27676 Re-platform from Cloudflare/Supabase to AWS Alex Maingot 2026-05-14 ↗ GitHub
commit body
Auth: 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>

Capture this thread into my fork

Download a single Markdown prompt that tells Claude how to port every commit above into your working tree — adapting paths and structure to match your repo. Run it via claude -p < capture-thread-440.md from inside the repo you want the changes in.

⬇ Download capture-thread-440.md