Skip to content

Migrate backend hosting: Railway → Oracle Cloud Always Free VM #169

Description

@Muawiya-contact

Goal
Move the backend (api + the pool-topup and reminders crons) off Railway onto an Oracle Cloud “Always Free” VM — free forever, and more capable than Railway’s small instance (Ampere A1 ARM: up to 4 cores / 24 GB RAM). Removes the Railway trial-credit time bomb and the ~$5/mo Hobby cost.

Why now / timing

  • The Railway workspace is on trial credit (was ~$4.72 / 22 days left) — when it runs out the backend stops. Stopgap: put Railway on **Hobby ($5/mo)** to avoid downtime, then do this migration on your own schedule. This is intentionally not urgent, but the credit deadline is real.

Prerequisite (do this first regardless of host): a custom API domain

  • The mobile app bakes EXPO_PUBLIC_API_BASE_URL (currently api-production-f3a9f.up.railway.app) into the OTA bundle, and it’s also referenced by the README APK link, the Supabase Auth redirect URL (/reset-password), and the /confirmed page. Changing the raw Railway/Oracle hostname would force a new mobile release and Supabase reconfig.
  • Point a custom domain (e.g. api.oneconcept.app) at the backend now, ship one mobile release that uses it, and update the Supabase redirect allowlist. After that, moving hosts is just a DNS change — no app release required. This de-risks the whole migration.

Scope — the migration chain

  1. Provision the Oracle Always Free VM (Ampere A1, ARM64), Ubuntu.
  2. Run the FastAPI app: the repo is already Dockerized (backend/Dockerfile) — build arm64 images; run via Docker + systemd (or docker compose) with restart-on-failure.
  3. Crons: run pool-topup (daily 22:00 UTC) and reminders (every 15 min) via system crontab/systemd timers — or refactor them into authed HTTP endpoints and schedule via GitHub Actions/cron-job.org (decouples crons from the host; see note).
  4. TLS + reverse proxy: Caddy (automatic Let’s Encrypt) or nginx+certbot in front of uvicorn, serving the custom domain.
  5. Secrets: move the Railway variable store to a locked-down .env on the VM (Supabase URL/keys, SUPABASE_ANON_KEY, SUPABASE_SERVICE_ROLE_KEY, GEMINI_API_KEY, FCM service-account, etc.).
  6. Deploy mechanism: Railway auto-deploys from main; on a VM you lose that — add a deploy path (GitHub Actions SSH git pull && restart, or a webhook). Update RELEASING.md (“Railway auto-deploys the api from main”) accordingly.
  7. Keep the /health keepalive (Supabase free pauses after ~7 days idle).
  8. DNS cutover: point the custom domain at the VM, verify, then decommission Railway.

Risks / caveats

  • ARM64: build images for linux/arm64; check all Python wheels (asyncpg, etc.) have ARM builds (they do).
  • You become the sysadmin: OS patching, TLS renewal (Caddy automates), log rotation, uptime. No managed dashboard — pairs with observability (No crash/error reporting or metrics anywhere; no React ErrorBoundary #161).
  • Oracle reclaims idle Always-Free instances — keep it genuinely active (the app + crons do) to avoid reclamation.
  • Card required at signup (Always Free itself doesn’t bill).

What this does NOT fix (separate issues): the email bottleneck (#152, still Supabase), the DB connection warmth (#149 — though a persistent VM process helps), unbounded /me/state (#150). The DB stays on Supabase.

Acceptance

  • Backend (api + both crons) runs on the Oracle VM behind the custom domain over HTTPS; /health green; reminders fire on schedule; a release still deploys cleanly (via the new deploy path); Railway decommissioned. No mobile release needed for the cutover (thanks to the custom domain).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions