# SEO Readiness Portal Cloud Migration — 2026-06-01

# SEO Readiness Portal Cloud Migration — 2026-06-01

Date: 2026-06-01
Owner: john
Verdict: DONE

## Live state verified (2026-06-01 05:31 UTC)

- `curl -sI https://seo-tools.alai.no/api/health` → HTTP 302 to `alai-no.cloudflareaccess.com/cdn-cgi/access/login/...` (CF Access enforced, no anonymous access)
- `curl -sI https://seo-readiness-alai.azurewebsites.net/api/health` → HTTP 403 `x-ms-forbidden-ip: 46.46.245.169` (Azure origin lock to Cloudflare IPs)

No traffic to John local host: cloudflared route for `seo-tools.alai.no` and `seo-tools.snowit.ba` set to `http_status:503`.

## Architecture (now)

Browser → Cloudflare Access (`seo-tools.alai.no`, alai-no team) → Cloudflare proxied DNS → Azure App Service Linux container `seo-readiness-alai` (Sweden Central, rg `rg-seo-readiness-prod`, asp `asp-seo-readiness-prod` B1) → Next.js standalone in `alairegistry.azurecr.io/seo-readiness-portal:20260531-cloud` (digest `sha256:16c8a40a...`) → persistent `/home/data/workspace.json`

App access mode: `SEO_PORTAL_ACCESS_MODE=cf-access`, trusted header `CF-Access-Authenticated-User-Email`, allowed domains `snowit.ba,alai.no`, extra allowed `alembasic@gmail.com`.

## Evidence

- Deploy summary: `/tmp/alai/seo-readiness-cloud-migration-20260531/cloud-deploy-summary.md`
- Local-disabled proof: `/tmp/alai/seo-readiness-cloud-migration-20260531/local-disabled-evidence.txt`
- Azure build + deploy: `azure-build.log`, `azure-webapp-deploy.log`, `azure-hostname-corrected.log`, `azure-role-settings.log`
- Origin lock: `azure-access-restrict-cloudflare.log`, `azure-access-restrict-smoke.log`
- DNS upsert: `cf-dns-upsert.log`
- Public smoke: `public-cloud-smoke.log`
- Origin smoke: `azure-smoke.log`
- Authenticated UAT (alem@alai.no, end-to-end: partners → add client → intake → audit → report → markdown export): `uat-alem/seo-cloud-uat-result.json` + 8 PNG screenshots `uat-alem/01-cf-login.png` … `08-export.png`
- Lesson memo: `lesson-seo-cloud-origin-lock-20260531.md`

## Docs corrected (no localhost as final target)

- `DEPLOYMENT-CLOUD.md` L7 — explicit "must not be served from John/local localhost"
- `DEPLOYMENT-INTERNAL.md` L7, L26, L102 — Docker steps marked local-dev only; cloud origin required for production; explicit "Do not route `seo-tools.*` to `http://127.0.0.1:*` or any localhost address"
- `README.md` L130 — "Azure App Service Linux container origin, not John/local localhost"
- `BUILD-BLUEPRINT.md` L54, L70 — Azure runbook section + CEO correction recorded

## Rollback

If Azure origin needs rollback:
1. Revert Web App container image: `az webapp config container set -g rg-seo-readiness-prod -n seo-readiness-alai --container-image-name alairegistry.azurecr.io/seo-readiness-portal:<previous-tag>`
2. CF Access policy and DNS remain unchanged; no public bypass introduced.
3. Do NOT re-enable cloudflared local route — that violates the CEO correction.

## Open follow-ups (separate MCs, not blockers)

- Postgres swap for `/home/data/workspace.json` (durable multi-user storage) — pre-req for external product use.
- `seo-tools.snowit.ba` DNS/zone provisioning (currently 503).
- Bind Azure custom hostname TLS managed cert (currently CF terminates).

## CEO scope check

- "Move off John/local host to real cloud" → DONE (Azure App Service)
- "Cloudflare Access trusted-header preserved" → DONE (`SEO_PORTAL_ACCESS_MODE=cf-access`)
- "No public unauthenticated access" → DONE (302 to CF Access login on unauth)
- "Custom domain toward seo-tools.alai.no" → DONE (CNAME + asuid TXT + Azure binding)
- "Concrete deploy URL/origin" → DONE (`seo-readiness-alai.azurewebsites.net` behind `seo-tools.alai.no`)
- "UAT evidence" → DONE (alem-authenticated screen-recorded flow)
- "Rollback" → documented above
- "Fix docs that recommend local host" → DONE (4 docs updated)


## MC / evidence references

- MC task: #102653
- Local closure artifact: `/tmp/alai/seo-readiness-cloud-migration-20260531/CLOSURE-102653.md`
- Cloud deploy summary: `/tmp/alai/seo-readiness-cloud-migration-20260531/cloud-deploy-summary.md`
- Authenticated UAT result: `/tmp/alai/seo-readiness-cloud-migration-20260531/uat-alem/seo-cloud-uat-result.json`
- Origin restriction smoke: `/tmp/alai/seo-readiness-cloud-migration-20260531/azure-access-restrict-smoke.log`
- P2P verifier PASS: `mesh-thr-50503688-7de8-489c-96ca-3d7d1a165dc2`

---

## See Also — Agent Runbook

The canonical end-to-end workflow runbook (intake to John deep-report, anti-pitfalls, trigger checklist):

- [SEO Pipeline — Portal Intake to John Deep-Report (agent runbook)](https://docs.alai.no/books/seo-readiness-portal/page/seo-pipeline-portal-intake-john-deep-report-agent-runbook)