Skip to content

Migrating from Upstash to Layerbase

8 min readUpstashRedisValkeyDatabases

Short version: you copy the keyspace into managed Redis or Valkey and swap Upstash's REST client for an ordinary Redis driver. The wizard takes your Upstash account email and a management API key, lists your databases, and copies every key, type, and TTL with a non-blocking SCAN, so your live cache keeps serving while it runs; by hand it is a short ioredis script that SCANs the keyspace and DUMPs and RESTOREs each key with its TTL against the rediss:// endpoint. It is Redis on both ends, so your keys, values, data types, TTLs, pub/sub, and Lua scripts port directly and nothing is left behind. The two things that genuinely change are the bill, flat per instance instead of per command, and the client, which only matters if you call Redis straight from an edge runtime that cannot open a TCP socket.

Upstash made serverless Redis easy: pay per request, scale to zero, connect over HTTP from the edge. That model is perfect when you're at zero, and it gets unpredictable as you grow, because the meter runs on every command. A chatty cache or a rate limiter doing millions of ops a day turns into a bill you can't forecast, and you don't own the instance, you rent commands against it.

If you'd rather pay a flat per-instance price for Redis-compatible storage you can actually run, Valkey on Layerbase Cloud is the move. Valkey is the BSD-licensed Redis fork, wire-compatible with everything you already use. Your data copies over with one API key, and the only code change is swapping Upstash's REST client for a standard Redis driver (and even that only matters on the edge).

Just want it done? Start at layerbase.com/migrate/upstash. You sign in, the wizard opens with Upstash already selected, and it copies every key, type, and TTL into managed Redis (or Valkey) in one non-blocking scan: read-once, nothing written back to Upstash. The rest of this post is that same migration explained step by step, plus the manual path.

Contents

What Actually Changes

Upstash speaks the Redis protocol, so your data structures, commands, and most of your code are unchanged. Two things differ:

  • The pricing model. Upstash meters per request (with a fixed-plan option on top). Layerbase Valkey is flat per instance: the bill is the same whether you do a thousand ops a month or a billion. For anything past hobby traffic, this is the whole point of moving.
  • The client. Upstash pushes @upstash/redis, a REST/HTTP client built for edge runtimes that can't open raw TCP sockets. A standard Valkey server speaks the Redis wire protocol over TCP, so on a normal Node/Python/Go server you use a normal client (ioredis, redis, redis-py). If you specifically need HTTP access from an edge function, see the client section below.

What does not change: your keys, values, data types, TTLs, pub/sub, Lua scripts, and the commands you call. It's Redis on both ends.

Set Up Valkey Locally with the Layerbase CLI

Stand up Valkey locally first so you can copy your data into it and verify before touching production. The Layerbase CLI (formerly SpinDB) runs it with one command, no Docker. (What is the Layerbase CLI?)

Install the Layerbase CLI:

bash
npm i -g layerbase    # npm
pnpm add -g layerbase # pnpm

Create a Valkey instance:

bash
lbase create upstash-migration -e valkey --start

Check the URL (it uses the redis:// scheme because Valkey speaks the same protocol):

bash
lbase url upstash-migration
text
redis://127.0.0.1:6380

Connect with redis-cli (or valkey-cli):

bash
lbase connect upstash-migration

Copy Your Data

The managed wizard (one API key)

On Layerbase Cloud, the create flow does the copy. Choose Migrating from another platform, pick Upstash, and paste your Upstash account email and a management API key (Upstash console, then Account, then Management API). It lists your Redis databases; for the one you pick it reads the whole keyspace with a non-blocking SCAN and copies every key, type, and TTL into a fresh managed Valkey. No redis-cli gymnastics, no blocking KEYS *.

By hand with a scan + dump

If you'd rather do it yourself (or copy into the local Layerbase instance above), Upstash gives you a rediss:// endpoint, so any Redis client can read it. One warning first: DUMP returns a binary payload, and the obvious redis-cli loop that stuffs it into a shell variable mangles it, so RESTORE rejects every key with a checksum error. A dozen lines of ioredis keep the bytes intact:

bash
npm i ioredis
SRC='rediss://default:<password>@<your-db>.upstash.io:6379' \
DST='redis://127.0.0.1:6380' \
node -e '
const Redis = require("ioredis")
const src = new Redis(process.env.SRC), dst = new Redis(process.env.DST)
;(async () => {
  let cursor = "0", n = 0
  do {
    const [next, keys] = await src.scan(cursor, "COUNT", 500)
    for (const key of keys) {
      const [dump, ttl] = await Promise.all([src.dumpBuffer(key), src.pttl(key)])
      if (dump) { await dst.restore(key, ttl > 0 ? ttl : 0, dump, "REPLACE"); n++ }
    }
    cursor = next
  } while (cursor !== "0")
  console.log(`copied ${n} keys`)
  await src.quit(); await dst.quit()
})()'

SCAN, never KEYS *, on a live database, and REPLACE makes the script safe to re-run. Tested against a Valkey 9 pair: strings containing NUL bytes, hashes with per-field TTLs, lists, sets, sorted sets, and a 90-second key expiry all came across byte for byte. For large keyspaces, prefer the managed wizard (it batches and resumes); the script is fine for small-to-medium caches. Upstash's serverless tier blocks BGSAVE/--rdb, which is why this is a per-key copy rather than an RDB file.

Swap the Client

If your app already uses a normal Redis client, you only change the connection string. The change that matters is for code using Upstash's REST client.

Before (Upstash REST client):

ts
import { Redis } from '@upstash/redis'

const redis = Redis.fromEnv() // UPSTASH_REDIS_REST_URL + token
await redis.set('session:42', value, { ex: 3600 })
const v = await redis.get('session:42')

After (standard Redis client over TCP):

ts
import Redis from 'ioredis'

const redis = new Redis(process.env.REDIS_URL!) // rediss://...cloud.layerbase.dev:port
await redis.set('session:42', value, 'EX', 3600)
const v = await redis.get('session:42')

Same commands, slightly different option syntax. If you use the @vercel/kv or @auth/upstash-redis-adapter packages, they accept any Redis-compatible client, so point them at ioredis.

A note on edge runtimes: @upstash/redis exists because some edge platforms can't open TCP sockets. If you deploy to a Node runtime (most apps), ioredis is simpler. If you genuinely need HTTP from an edge function, keep that path behind a small Node API route that talks to Valkey over TCP.

What to Test

  • Run your suite against the local Valkey. Commands behave identically; the protocol hasn't changed.
  • Check TTL-sensitive logic. Confirm sessions, rate-limit windows, and cache expiries carried their TTLs across (the wizard and the DUMP/RESTORE loop both preserve them; verify a few keys with PTTL).
  • Check connection handling. The REST client was stateless; with ioredis use a single client/pool per instance rather than constructing one per request, especially in serverless.
  • If you used Upstash for a queue, note that Layerbase's managed queue add-on, vqueue, runs on Valkey; a plain key/value copy brings your data, and the queue is a separate product.

The Managed Path: Layerbase Cloud

The hands-on version is worth doing once. When you want managed Valkey you don't run yourself, Layerbase Cloud provisions it in seconds with TLS, backups, and flat per-instance pricing, and the Migrating from another platform wizard does the copy from your Upstash API key. Off the per-command meter, onto an instance you own.

FAQ

Which credentials does the wizard want?

Upstash is a two-part key, so it asks for your account email alongside a management API key from the Upstash console (Account, then Management API). With both it lists your Redis databases and you pick one. If you would rather not mint a management key, the wizard also accepts a plain rediss:// TLS endpoint copied from the database's Connect tab, which points at exactly one database and skips the listing step. The REST URL and token are not an option either way, because copying a keyspace needs the wire protocol.

Does the copy interrupt my live cache?

No. The read uses a non-blocking SCAN rather than KEYS *, so Upstash keeps serving reads and writes throughout, and nothing is ever written back to the source. The gap to plan for is the one after the copy finishes: keys written to Upstash before you flip the client stay on Upstash, so cut over promptly or run the copy during a quiet window.

Do TTLs actually survive?

Yes, and it is the thing to verify first. The wizard copies the remaining TTL with each key, and the manual DUMP/RESTORE script preserves it too, which is why the PTTL value is carried through explicitly rather than reset. Check a few sessions, a rate-limit window, and a cache entry with PTTL on the new instance before you trust the whole keyspace.

Do I have to stop using @upstash/redis?

On a normal Node, Python, or Go server, yes, and it is an improvement: ioredis or redis talks the wire protocol over TCP with the same commands and slightly different option syntax. @upstash/redis only exists because some edge runtimes cannot open raw sockets, so if you genuinely call Redis from an edge function, keep that path behind a small Node API route that speaks TCP to Valkey. Packages like @vercel/kv and @auth/upstash-redis-adapter accept any Redis-compatible client, so they take ioredis as-is.

Redis or Valkey on the other side?

Wire-compatible either way, so your commands and data structures are identical and the copy is the same. Pick Valkey if you would rather not depend on the Redis license, or Redis if you want exact upstream. One thing does decide it for you: if Upstash was backing a message queue, land on Valkey, because the managed queue add-on vqueue runs on Valkey and a key/value copy alone does not bring a queue product with it.

Wrapping Up

Leaving Upstash is mostly a data copy plus a one-line client swap. It's Redis on both ends, so your commands and data structures port directly; the wins are predictable pricing and, on a Node runtime, a simpler standard client. Copy your keyspace with one API key through the wizard, or a --scan + DUMP/RESTORE loop by hand.

Manage your local Valkey instance with the Layerbase CLI:

bash
lbase stop upstash-migration    # Stop the server
lbase start upstash-migration   # Start it again
lbase url upstash-migration     # Print the connection URL
lbase list                      # See all your instances

The Layerbase CLI handles 20+ database engines, so Valkey can sit next to your Postgres, Meilisearch, or Qdrant while you verify the move. Layerbase Desktop wraps it in a GUI on macOS. For the Redis-to-Valkey background, see Redis vs Valkey and Migrating from Redis to Valkey.