Skip to content

chore: remove Fly.io infrastructure, use Electric Cloud#1717

Merged
saddlepaddle merged 1 commit intomainfrom
saddlepaddle/move-off-fly
Feb 23, 2026
Merged

chore: remove Fly.io infrastructure, use Electric Cloud#1717
saddlepaddle merged 1 commit intomainfrom
saddlepaddle/move-off-fly

Conversation

@saddlepaddle
Copy link
Collaborator

@saddlepaddle saddlepaddle commented Feb 23, 2026

Summary

  • Delete fly.toml and remove all Fly.io deployment jobs from production, preview, and cleanup CI workflows
  • API Electric proxy now prefers Electric Cloud (ELECTRIC_SOURCE_ID/ELECTRIC_SOURCE_SECRET) and falls back to local Docker (ELECTRIC_URL/ELECTRIC_SECRET) for dev
  • Remove X-Electric-Backend header from CORS config and desktop client
  • Update desktop streams fallback URL from superset-stream.fly.dev to streams.superset.sh
  • Remove Electric (Fly.io) row from PR preview comment template

Post-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_ORG variable (repo-level)

Add these GitHub secrets (production env):

  • ELECTRIC_SOURCE_ID
  • ELECTRIC_SOURCE_SECRET

Test plan

  • bun run lint:fix — clean
  • bun run typecheck — 18/18 passed
  • bun test — 1342/1342 passed
  • Verify local dev with ELECTRIC_URL/ELECTRIC_SECRET pointing at Docker
  • Verify production deploy with ELECTRIC_SOURCE_ID/ELECTRIC_SOURCE_SECRET

Summary by CodeRabbit

  • Chores
    • Removed Electric (Fly.io) service deployment and cleanup steps from CI/CD workflows, streamlining the deployment pipeline.
    • Transitioned Electric authentication from URL-based to cloud source credentials approach.
    • Updated infrastructure service endpoint from Fly.io domain to new superset.sh domain.
    • Removed Fly.io deployment configuration file.

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.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 23, 2026

📝 Walkthrough

Walkthrough

These 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

Cohort / File(s) Summary
GitHub Workflows - Electric Service Removal
.github/workflows/cleanup-preview.yml, .github/workflows/deploy-preview.yml, .github/workflows/deploy-production.yml
Removed Electric (Fly.io) deployment jobs, cleanup steps, and status reporting. Deleted entire deploy-electric job and related setup/cleanup tasks across workflows.
Environment Variables - Configuration Migration
apps/api/src/env.ts
Made ELECTRIC_URL and ELECTRIC_SECRET optional in server schema, enabling fallback to new source-based configuration (ELECTRIC_SOURCE_ID/SECRET).
API Routing Logic
apps/api/src/app/api/electric/[...path]/route.ts
Updated Electric endpoint routing to support dual configurations: source-ID based (cloud endpoint) and legacy URL-based, with early error return for missing config.
CORS and Headers
apps/api/src/proxy.ts, apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/collections.ts
Removed X-Electric-Backend header from CORS allow-headers and from Electric request headers.
Configuration Updates
apps/desktop/electron.vite.config.ts
Updated STREAMS_URL default from Fly.io subdomain to superset.sh domain in both main and renderer process configurations.
Deployment Configuration
fly.toml, .github/templates/preview-comment.md
Removed fly.toml configuration file and Electric service row from preview status comment template.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 Farewell to Fly.io's Electric spark,
Source IDs guide us through the dark,
Headers shed, configs refold,
New paths emerge as workflows unfold!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and accurately summarizes the main change: removing Fly.io infrastructure and adopting Electric Cloud.
Description check ✅ Passed The description includes all key sections from the template and provides comprehensive details about changes, manual cleanup steps, and a test plan.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch saddlepaddle/move-off-fly

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between e37836b and b8b17c8.

📒 Files selected for processing (10)
  • .github/templates/preview-comment.md
  • .github/workflows/cleanup-preview.yml
  • .github/workflows/deploy-preview.yml
  • .github/workflows/deploy-production.yml
  • apps/api/src/app/api/electric/[...path]/route.ts
  • apps/api/src/env.ts
  • apps/api/src/proxy.ts
  • apps/desktop/electron.vite.config.ts
  • apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/collections.ts
  • fly.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

Comment on lines +156 to +157
ELECTRIC_SOURCE_ID: ${{ secrets.ELECTRIC_SOURCE_ID }}
ELECTRIC_SOURCE_SECRET: ${{ secrets.ELECTRIC_SOURCE_SECRET }}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Comment on lines +106 to +107
ELECTRIC_SOURCE_ID: ${{ secrets.ELECTRIC_SOURCE_ID }}
ELECTRIC_SOURCE_SECRET: ${{ secrets.ELECTRIC_SOURCE_SECRET }}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Comment on lines +35 to 47
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 },
);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Suggested change
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).

Comment on lines +15 to +16
ELECTRIC_URL: z.string().url().optional(),
ELECTRIC_SECRET: z.string().optional(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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.

Suggested change
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.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 23, 2026

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ✅ Neon database branch

Thank you for your contribution! 🎉

@saddlepaddle saddlepaddle merged commit 3a87431 into main Feb 23, 2026
13 checks passed
saddlepaddle added a commit that referenced this pull request Feb 23, 2026
saddlepaddle added a commit that referenced this pull request Feb 23, 2026
@Kitenite Kitenite deleted the saddlepaddle/move-off-fly branch February 27, 2026 09:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant