Migrate to koigrid
Move your stack from AWS/Vercel/Railway/Render piece by piece — each step is reversible. Postgres migrates with near-zero downtime via logical replication (CDC), so you cut over when lag is 0.
1. Postgres
MANAGED RESTORE (easiest): upload your dump and koigrid imports it — POST /databases/:id/restore-dump/upload-url {gzip:true} → PUT the .sql.gz → POST /databases/:id/restore-dump {dumpKey}, or koigrid db restore-dump <id> --file dump.sql --wait (the CLI gzips and streams it). It pre-seeds extensions (fixes the Supabase extensions.vector mismatch), is fail-fast with the ROOT-CAUSE error, atomic (a failure leaves the DB untouched, so a retry reports the same cause), keeps your dump so a retry costs no re-upload, and hands ownership to your app role so the data is readable right away. Verify with the per-table row counts it returns. DIY alternative: dump + restore. Near-zero-downtime: logical replication streams your source into koigrid; cut over at lag 0. For a big restore, connect to the DIRECT write port (not the PgBouncer pool) — transaction pooling breaks COPY and long restores.
# 1) Create the managed database — match your SOURCE major version (14-17) to avoid dump mismatches
koigrid db create prod --version 17
DATABASE_URL="$(koigrid db connection <id> --json | jq -r .connection.uri)"
# --- Simple (dump + restore) ---
# For a BIG restore, connect to the DIRECT write port (connection.writeUri), NOT the pool: transaction
# pooling breaks COPY and long restores (missing tables/indexes).
pg_dump "$SOURCE_URL" | psql "$DATABASE_URL" # or: drizzle-kit migrate / prisma migrate deploy
# ex-Supabase? install extensions into their schema:
# POST /databases/:id/extensions {"name":"uuid-ossp","schema":"extensions"} (koigrid does it as superuser)
# --- Near-zero-downtime (logical replication / CDC) ---
# a) on your SOURCE (e.g. RDS with rds.logical_replication=1):
# CREATE PUBLICATION mig FOR ALL TABLES;
# b) copy the SCHEMA to koigrid first (tables must exist):
pg_dump --schema-only "$SOURCE_URL" | psql "$DATABASE_URL"
# c) on koigrid (the app user has REPLICATION — no superuser needed):
# CREATE SUBSCRIPTION mig CONNECTION 'postgresql://user:pass@source-host:5432/db' PUBLICATION mig;
# d) watch lag → 0: SELECT * FROM pg_stat_subscription;
# e) cut over (point the app at koigrid), then: DROP SUBSCRIPTION mig;
# --- After migrating: automatic password rotation (AWS Secrets Manager rotation substitute) ---
# ZERO-DOWNTIME dual-role rotation. One-time: switch the pooler to reloadable mode, then schedule:
koigrid db rotation <id> migrate # brief reconnect, once
koigrid db rotation <id> set --interval 30 # rotate every 30 days (or: koigrid db rotation <id> rotate)
# apps wiring DATABASE_URL='${{db.<name>.DATABASE_URL}}' auto-redeploy with the fresh password.2. Storage (S3)
Your bucket already speaks S3 — copy objects with rclone or aws-cli, then repoint your client’s endpoint + keys.
koigrid storage buckets create assets koigrid storage keys # → accessKey / secretKey / endpoint (shown once) # copy objects from your old S3 (rclone remote 'koigrid' → https://s3.koigrid.com, path-style) rclone copy s3old:my-bucket koigrid:assets
ZERO code change: koigrid supports virtual-hosted-style (bucket.s3.koigrid.com), so the standard AWS SDK just works — set AWS_ENDPOINT_URL_S3 + your keys as env vars and keep your code as-is:
# Your app's S3 code stays EXACTLY as-is. Just set env vars: AWS_ENDPOINT_URL_S3=https://s3.koigrid.com AWS_ACCESS_KEY_ID=<from koigrid storage keys> AWS_SECRET_ACCESS_KEY=<from koigrid storage keys> AWS_REGION=us-east-1 # any value; koigrid is region-agnostic # koigrid supports virtual-hosted-style (bucket.s3.koigrid.com), so the standard # new S3Client() works with no forcePathStyle and no code change. Use the bucketName # koigrid returns (koi-<org>-<name>-xxxx) as the Bucket.
3. App
Deploy from a git repo, a container image, or your local folder (--dir, no git/Docker needed). Set env vars, then a custom domain. The CDN is on by default and caches your HTML at the edge honoring Cache-Control (send s-maxage=N; Next.js ISR already does) — CloudFront-style edge latency, no extra config; protected apps and previews are never edge-cached.
# from a git repo (add --repo-token for a private repo):
koigrid apps deploy web --repo https://github.com/you/app
# ...or straight from your local folder (no git, no Docker):
koigrid apps deploy web --dir ./
# env vars (invalid lines are reported, not fatal; saved even before the first deploy):
curl -X POST $API/apps/<id>/env -H "Authorization: Bearer koi_KEY" \
-d '{"env":{"DATABASE_URL":"...","API_KEY":"..."}}'
# custom domain + automatic TLS:
koigrid apps domains web app.yourdomain.com4. Cron jobs
Your EventBridge/cron endpoints become HTTP jobs — koigrid hits the URL on schedule with your secret header.
# an EventBridge/cron endpoint → an HTTP job (koigrid calls the URL on schedule): koigrid jobs create nightly --schedule "0 3 * * *" \ --url https://app.yourdomain.com/api/cron/report --header "x-cron-secret: $SECRET" # ...or a container job: koigrid jobs create backup --schedule "0 2 * * *" --image alpine --command "sh -c '…'"
5. Cutover
Point DNS at koigrid with a rollback ready. Bandwidth is included and flat — no egress surprise on the way out of AWS.
# point your DNS at koigrid, keep the old stack ready to roll back. # bandwidth is included + flat — no egress bill on the way out of AWS.