chore: remove Fly.io infrastructure, use Electric Cloud#1717
chore: remove Fly.io infrastructure, use Electric Cloud#1717saddlepaddle merged 1 commit intomainfrom
Conversation
Remove all Fly.io deployment infrastructure (fly.toml, deploy-electric CI jobs, preview app provisioning/cleanup) and switch production Electric SQL to the managed Electric Cloud API. Local dev still works via ELECTRIC_URL/ ELECTRIC_SECRET for the Docker container.
📝 WalkthroughWalkthroughThese changes remove the Fly.io-deployed Electric service from preview and production deployment workflows, replacing URL-based Electric integration with source ID-based configuration. The Electric deployment job is deleted, environment variables are refactored, and related headers and configuration files are removed. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.github/workflows/deploy-preview.yml:
- Around line 156-157: The deploy job scoped to environment: preview is using
secrets ELECTRIC_SOURCE_ID and ELECTRIC_SOURCE_SECRET but those secrets were
only added to the production environment; add ELECTRIC_SOURCE_ID and
ELECTRIC_SOURCE_SECRET to the preview GitHub environment as well so the
deploy-api job (environment: preview) can resolve ${{ secrets.ELECTRIC_SOURCE_ID
}} and ${{ secrets.ELECTRIC_SOURCE_SECRET }} during PR preview deploys.
In @.github/workflows/deploy-production.yml:
- Around line 106-107: Pre-stage the two secrets ELECTRIC_SOURCE_ID and
ELECTRIC_SOURCE_SECRET in the production GitHub environment before merging (they
must exist when the workflow that runs on pushes to main evaluates ${{
secrets.ELECTRIC_SOURCE_ID }} and ${{ secrets.ELECTRIC_SOURCE_SECRET }}), and
update the PR description to explicitly instruct adding these secrets pre-merge;
alternatively, add a runtime guard in the workflow to fail the job when either
secret is empty so a post-merge blank-secret deploy cannot silently succeed.
In `@apps/api/src/app/api/electric/`[...path]/route.ts:
- Around line 35-47: The local branch builds originUrl incorrectly by calling
new URL(ELECTRIC_URL) which omits the '/v1/shape' path; update the branch that
handles ELECTRIC_URL/ELECTRIC_SECRET so originUrl is created using the base plus
'/v1/shape' (e.g., new URL('/v1/shape', ELECTRIC_URL)) and then set the "secret"
search param on that URL (referencing originUrl, ELECTRIC_URL, ELECTRIC_SECRET
in route.ts).
In `@apps/api/src/env.ts`:
- Around line 15-16: ELECTRIC_SECRET's schema lost its minimum-length
validation; update the zod schema for the ELECTRIC_SECRET entry in env.ts to
require a minimum length when present by changing its validator to use min(16)
before making it optional (i.e., use z.string().min(16).optional()), so the
field can still be omitted but any provided secret must be at least 16
characters.
ℹ️ Review info
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (10)
.github/templates/preview-comment.md.github/workflows/cleanup-preview.yml.github/workflows/deploy-preview.yml.github/workflows/deploy-production.ymlapps/api/src/app/api/electric/[...path]/route.tsapps/api/src/env.tsapps/api/src/proxy.tsapps/desktop/electron.vite.config.tsapps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/collections.tsfly.toml
💤 Files with no reviewable changes (4)
- .github/templates/preview-comment.md
- .github/workflows/cleanup-preview.yml
- apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/collections.ts
- fly.toml
| ELECTRIC_SOURCE_ID: ${{ secrets.ELECTRIC_SOURCE_ID }} | ||
| ELECTRIC_SOURCE_SECRET: ${{ secrets.ELECTRIC_SOURCE_SECRET }} |
There was a problem hiding this comment.
ELECTRIC_SOURCE_ID / ELECTRIC_SOURCE_SECRET must also be added to the preview GitHub environment.
The deploy-api job runs under environment: preview (Line 80). GitHub Actions resolves ${{ secrets.* }} from the environment that the job is scoped to, so these two secrets must exist in both the preview and production environments.
The post-merge instructions in the PR description only say:
Add these GitHub secrets to production env:
ELECTRIC_SOURCE_ID,ELECTRIC_SOURCE_SECRET
If only production is updated, every preview-PR deploy will receive empty values for ELECTRIC_SOURCE_ID and ELECTRIC_SOURCE_SECRET, silently breaking Electric sync for all preview environments after this merges. The same secrets (pointing at the same Electric Cloud source, or a dedicated one) need to be configured in the preview environment too.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/deploy-preview.yml around lines 156 - 157, The deploy job
scoped to environment: preview is using secrets ELECTRIC_SOURCE_ID and
ELECTRIC_SOURCE_SECRET but those secrets were only added to the production
environment; add ELECTRIC_SOURCE_ID and ELECTRIC_SOURCE_SECRET to the preview
GitHub environment as well so the deploy-api job (environment: preview) can
resolve ${{ secrets.ELECTRIC_SOURCE_ID }} and ${{ secrets.ELECTRIC_SOURCE_SECRET
}} during PR preview deploys.
| ELECTRIC_SOURCE_ID: ${{ secrets.ELECTRIC_SOURCE_ID }} | ||
| ELECTRIC_SOURCE_SECRET: ${{ secrets.ELECTRIC_SOURCE_SECRET }} |
There was a problem hiding this comment.
Pre-stage ELECTRIC_SOURCE_ID / ELECTRIC_SOURCE_SECRET in the production environment before merging.
The PR description lists adding these secrets as a post-merge step. However, the workflow triggers on every push to main, so the very first production deployment after merge will resolve both secrets to empty strings (${{ secrets.ELECTRIC_SOURCE_ID }} → "" when unset). Vercel will deploy successfully with ELECTRIC_SOURCE_ID= and ELECTRIC_SOURCE_SECRET= set to blank — no pipeline failure, but Electric Cloud connectivity will break silently at runtime.
The fix is to add both secrets to the production GitHub environment before this PR is merged, not after.
Also applies to: 155-156
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/deploy-production.yml around lines 106 - 107, Pre-stage
the two secrets ELECTRIC_SOURCE_ID and ELECTRIC_SOURCE_SECRET in the production
GitHub environment before merging (they must exist when the workflow that runs
on pushes to main evaluates ${{ secrets.ELECTRIC_SOURCE_ID }} and ${{
secrets.ELECTRIC_SOURCE_SECRET }}), and update the PR description to explicitly
instruct adding these secrets pre-merge; alternatively, add a runtime guard in
the workflow to fail the job when either secret is empty so a post-merge
blank-secret deploy cannot silently succeed.
| if (ELECTRIC_SOURCE_ID && ELECTRIC_SOURCE_SECRET) { | ||
| originUrl = new URL("/v1/shape", "https://api.electric-sql.cloud"); | ||
| originUrl.searchParams.set("source_id", ELECTRIC_SOURCE_ID); | ||
| originUrl.searchParams.set("source_secret", ELECTRIC_SOURCE_SECRET); | ||
| } else if (ELECTRIC_URL && ELECTRIC_SECRET) { | ||
| originUrl = new URL(ELECTRIC_URL); | ||
| originUrl.searchParams.set("secret", ELECTRIC_SECRET); | ||
| } else { | ||
| originUrl.searchParams.set("secret", env.ELECTRIC_SECRET); | ||
| return new Response( | ||
| "Missing Electric config: set ELECTRIC_SOURCE_ID/SECRET or ELECTRIC_URL/SECRET", | ||
| { status: 500 }, | ||
| ); | ||
| } |
There was a problem hiding this comment.
Local Electric URL is missing the /v1/shape path.
The cloud branch (line 36) explicitly constructs new URL("/v1/shape", "https://api.electric-sql.cloud"). The local branch (line 40) does new URL(ELECTRIC_URL) verbatim — no path is appended.
Electric SQL's official auth guide constructs the upstream URL as new URL('http://localhost:3000/v1/shape'), and their blog example uses new URL('/v1/shape', baseUrl) where baseUrl = process.env.ELECTRIC_URL. This treats ELECTRIC_URL as a base origin, not a full endpoint. If a developer follows that convention and sets ELECTRIC_URL=http://localhost:3000, requests will hit the root path and Electric will return a 404/400.
The fix aligns the local branch with both the cloud branch and Electric SQL's documented pattern:
🐛 Proposed fix
- } else if (ELECTRIC_URL && ELECTRIC_SECRET) {
- originUrl = new URL(ELECTRIC_URL);
- originUrl.searchParams.set("secret", ELECTRIC_SECRET);
+ } else if (ELECTRIC_URL && ELECTRIC_SECRET) {
+ originUrl = new URL("/v1/shape", ELECTRIC_URL);
+ originUrl.searchParams.set("secret", ELECTRIC_SECRET);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (ELECTRIC_SOURCE_ID && ELECTRIC_SOURCE_SECRET) { | |
| originUrl = new URL("/v1/shape", "https://api.electric-sql.cloud"); | |
| originUrl.searchParams.set("source_id", ELECTRIC_SOURCE_ID); | |
| originUrl.searchParams.set("source_secret", ELECTRIC_SOURCE_SECRET); | |
| } else if (ELECTRIC_URL && ELECTRIC_SECRET) { | |
| originUrl = new URL(ELECTRIC_URL); | |
| originUrl.searchParams.set("secret", ELECTRIC_SECRET); | |
| } else { | |
| originUrl.searchParams.set("secret", env.ELECTRIC_SECRET); | |
| return new Response( | |
| "Missing Electric config: set ELECTRIC_SOURCE_ID/SECRET or ELECTRIC_URL/SECRET", | |
| { status: 500 }, | |
| ); | |
| } | |
| if (ELECTRIC_SOURCE_ID && ELECTRIC_SOURCE_SECRET) { | |
| originUrl = new URL("/v1/shape", "https://api.electric-sql.cloud"); | |
| originUrl.searchParams.set("source_id", ELECTRIC_SOURCE_ID); | |
| originUrl.searchParams.set("source_secret", ELECTRIC_SOURCE_SECRET); | |
| } else if (ELECTRIC_URL && ELECTRIC_SECRET) { | |
| originUrl = new URL("/v1/shape", ELECTRIC_URL); | |
| originUrl.searchParams.set("secret", ELECTRIC_SECRET); | |
| } else { | |
| return new Response( | |
| "Missing Electric config: set ELECTRIC_SOURCE_ID/SECRET or ELECTRIC_URL/SECRET", | |
| { status: 500 }, | |
| ); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/api/src/app/api/electric/`[...path]/route.ts around lines 35 - 47, The
local branch builds originUrl incorrectly by calling new URL(ELECTRIC_URL) which
omits the '/v1/shape' path; update the branch that handles
ELECTRIC_URL/ELECTRIC_SECRET so originUrl is created using the base plus
'/v1/shape' (e.g., new URL('/v1/shape', ELECTRIC_URL)) and then set the "secret"
search param on that URL (referencing originUrl, ELECTRIC_URL, ELECTRIC_SECRET
in route.ts).
| ELECTRIC_URL: z.string().url().optional(), | ||
| ELECTRIC_SECRET: z.string().optional(), |
There was a problem hiding this comment.
ELECTRIC_SECRET loses its minimum-length guard.
The previous z.string().min(16) provided a sanity floor when the secret was provided. The new z.string().optional() accepts any length (including a 1-character value) when set for local dev. Consider z.string().min(16).optional() to preserve the strength check while still allowing the field to be absent.
🛡️ Proposed fix
- ELECTRIC_SECRET: z.string().optional(),
+ ELECTRIC_SECRET: z.string().min(16).optional(),📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| ELECTRIC_URL: z.string().url().optional(), | |
| ELECTRIC_SECRET: z.string().optional(), | |
| ELECTRIC_URL: z.string().url().optional(), | |
| ELECTRIC_SECRET: z.string().min(16).optional(), |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/api/src/env.ts` around lines 15 - 16, ELECTRIC_SECRET's schema lost its
minimum-length validation; update the zod schema for the ELECTRIC_SECRET entry
in env.ts to require a minimum length when present by changing its validator to
use min(16) before making it optional (i.e., use z.string().min(16).optional()),
so the field can still be omitted but any provided secret must be at least 16
characters.
🧹 Preview Cleanup CompleteThe following preview resources have been cleaned up:
Thank you for your contribution! 🎉 |
Summary
fly.tomland remove all Fly.io deployment jobs from production, preview, and cleanup CI workflowsELECTRIC_SOURCE_ID/ELECTRIC_SOURCE_SECRET) and falls back to local Docker (ELECTRIC_URL/ELECTRIC_SECRET) for devX-Electric-Backendheader from CORS config and desktop clientsuperset-stream.fly.devtostreams.superset.shPost-merge manual cleanup
Remove these GitHub secrets/variables:
FLY_API_TOKEN(repo-level)ELECTRIC_SECRET_PREVIEW(repo-level)ELECTRIC_URL(production env)ELECTRIC_SECRET(production env)FLY_ORGvariable (repo-level)Add these GitHub secrets (production env):
ELECTRIC_SOURCE_IDELECTRIC_SOURCE_SECRETTest plan
bun run lint:fix— cleanbun run typecheck— 18/18 passedbun test— 1342/1342 passedELECTRIC_URL/ELECTRIC_SECRETpointing at DockerELECTRIC_SOURCE_ID/ELECTRIC_SOURCE_SECRETSummary by CodeRabbit