# Migrating from Upstash to Layerbase

> Upstash bills per request, which is great at zero traffic and surprising at scale. Here is how to move to flat-priced managed Valkey on Layerbase: copy every key with one API key, and swap the REST client for a standard Redis driver.

Published: 2026-06-23
Canonical: https://layerbase.com/blog/migrating-from-upstash-to-layerbase

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 `SCAN`s the keyspace and `DUMP`s and `RESTORE`s 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](https://cloud.layerbase.com/create?engine=valkey) 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](https://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](#what-actually-changes)
- [Set Up Valkey Locally with the Layerbase CLI](#set-up-valkey-locally-with-the-layerbase-cli)
- [Copy Your Data](#copy-your-data)
- [Swap the Client](#swap-the-client)
- [What to Test](#what-to-test)
- [The Managed Path: Layerbase Cloud](#the-managed-path-layerbase-cloud)
- [FAQ](#faq)

## 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](https://layerbase.com/docs/cli) (formerly SpinDB) runs it with one command, no Docker. ([What is the Layerbase CLI?](https://layerbase.com/docs/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](https://cloud.layerbase.com/create?engine=valkey), 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](/addon/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](https://cloud.layerbase.com/create?engine=valkey) 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](/addon/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](https://layerbase.com/docs/cli), so Valkey can sit next to your Postgres, Meilisearch, or Qdrant while you verify the move. [Layerbase Desktop](https://layerbase.com/desktop/download) wraps it in a GUI on macOS. For the Redis-to-Valkey background, see [Redis vs Valkey](/blog/redis-vs-valkey) and [Migrating from Redis to Valkey](/blog/migrating-from-redis-to-valkey).
