Create your own Upstash Redis
This workflow boots a dedicated Redis instance inside an AerolVM sandbox, initializes it from environment variables, and keeps the database alive in a long-running session. It also starts a tiny HTTP admin endpoint that checks the database and can be exposed on a public URL.
This is the Redis foundation, providing a serverless-like experience where each user or project gets its own isolated instance.
Native Redis endpoint
Section titled “Native Redis endpoint”- via
exposePortwithprotocol: "tcp"
This page exposes the Redis protocol directly using exposePort(6379, { protocol: "tcp" }). The daemon allocates a parent-host port from the configured pool and points caddy-l4 at the container's 6379, so the returned result has a tcp://<public-host>:<host-port> URL. This works with redis-cli, Ioredis, StackExchange.Redis, and any other client that speaks the Redis protocol.
If you only need browser-shaped traffic (a small admin UI, REST shim), call exposePort() with no options - that's the HTTPS-only path.
Copy-paste TypeScript script
Section titled “Copy-paste TypeScript script”Set these environment variables before running the script:
SB_PAT_TOKENREDIS_PASSWORD- Optional:
PUBLIC_AUTH_USERandPUBLIC_AUTH_PASSWORDfor the HTTP admin endpoint.
If SB_API_URL is unset, the script defaults to http://127.0.0.1:21212.
Code Location: https://github.com/aerol-ai/aerolvm-examples/blob/main/customer-facing/create-upstash-redis/index.ts
import { writeFile } from "node:fs/promises";import { setTimeout as delay } from "node:timers/promises";import { MicroVM } from "@aerol-ai/aerolvm-sdk";
const apiUrl = process.env.SB_API_URL ?? "http://127.0.0.1:21212";const patToken = process.env.SB_PAT_TOKEN;const redisPassword = process.env.REDIS_PASSWORD ?? 'password';const publicAuthUser = process.env.PUBLIC_AUTH_USER ?? 'admin';const publicAuthPassword = process.env.PUBLIC_AUTH_PASSWORD ?? redisPassword;
const redisPort = 6379;const adminPort = 3000;
if (!patToken) { throw new Error("Set SB_PAT_TOKEN before running this example.");}
const redisURL = `redis://:${encodeURIComponent(redisPassword)}@127.0.0.1:${redisPort}`;
const adminServer = `import base64import jsonimport osfrom http.server import BaseHTTPRequestHandler, ThreadingHTTPServerimport redis
REDIS_URL = os.environ.get("REDIS_URL", "redis://127.0.0.1:6379")AUTH_TOKEN = os.environ.get("PUBLIC_AUTH_TOKEN")PORT = int(os.environ.get("PORT", "3000"))
r = redis.Redis.from_url(REDIS_URL, decode_responses=True)
def _encode(value, encoding): # Mirror Upstash REST conventions: booleans are returned as 1/0 ints, # and when the client sent "Upstash-Encoding: base64" all string values # are base64-encoded (except the literal "OK" status reply). if isinstance(value, bool): return 1 if value else 0 if isinstance(value, list): return [_encode(v, encoding) for v in value] if isinstance(value, dict): return {k: _encode(v, encoding) for k, v in value.items()} if encoding == "base64" and isinstance(value, str) and value != "OK": return base64.b64encode(value.encode("utf-8")).decode("ascii") return value
class Handler(BaseHTTPRequestHandler): def do_GET(self) -> None: if self.path == "/healthz": self._send_response(200, b"ok\\n", "text/plain") return self.send_error(404)
def do_POST(self) -> None: if not self._authenticate(): return
content_length = int(self.headers.get("Content-Length", 0)) body = self.rfile.read(content_length).decode("utf-8")
try: command_data = json.loads(body) except json.JSONDecodeError: self.send_error(400, "Invalid JSON") return
path = self.path.rstrip("/") encoding = self.headers.get("Upstash-Encoding", "").lower()
try: if path == "" or path == "/": # Single command: ["SET", "key", "val"] result = r.execute_command(*command_data) self._send_json({"result": _encode(result, encoding)}) elif path == "/pipeline" or path == "/multi-exec": # List of commands: [["SET", "a", "b"], ["GET", "a"]] pipe = r.pipeline(transaction=(path == "/multi-exec")) for cmd in command_data: pipe.execute_command(*cmd) results = pipe.execute() self._send_json([{"result": _encode(res, encoding)} for res in results]) else: # Command in path: /set/key/value parts = path.strip("/").split("/") result = r.execute_command(*parts) self._send_json({"result": _encode(result, encoding)}) except Exception as e: self._send_json({"error": str(e)}, status=500)
def _authenticate(self) -> bool: if not AUTH_TOKEN: return True auth_header = self.headers.get("Authorization", "") if auth_header == f"Bearer {AUTH_TOKEN}": return True self.send_response(401) self.end_headers() return False
def _send_json(self, data, status=200) -> None: body = json.dumps(data).encode("utf-8") self._send_response(status, body, "application/json")
def _send_response(self, status, body, content_type) -> None: self.send_response(status) self.send_header("Content-Type", f"{content_type}; charset=utf-8") self.send_header("Content-Length", str(len(body))) self.send_header("Access-Control-Allow-Origin", "*") self.end_headers() self.wfile.write(body)
def log_message(self, format: str, *args) -> None: return
ThreadingHTTPServer(("0.0.0.0", PORT), Handler).serve_forever()`;
function log(msg: string) { console.log(`[${new Date().toISOString()}] ${msg}`);}
async function withRetry<T>(label: string, fn: () => Promise<T>, attempts = 5): Promise<T> { let lastErr: unknown; for (let i = 0; i < attempts; i += 1) { try { return await fn(); } catch (err) { lastErr = err; const wait = Math.min(8000, 1000 * 2 ** i); const cause = (err as { cause?: { code?: string } } | undefined)?.cause?.code; log(` ${label}: attempt ${i + 1}/${attempts} failed (${cause ?? (err as Error).message}); retrying in ${wait}ms`); await delay(wait); } } throw lastErr;}
async function waitForRedis(sandbox: Awaited<ReturnType<MicroVM["create"]>>) { log("Waiting for Redis to accept connections..."); for (let attempt = 0; attempt < 30; attempt += 1) { const result = await sandbox.exec({ command: `redis-cli -a "${redisPassword}" ping`, timeoutSeconds: 5, });
if (result.exitCode === 0 && result.stdout.trim() === "PONG") { log(`Redis ready after ${attempt + 1} attempt(s)`); return; }
log(` attempt ${attempt + 1}/30: not ready yet (exitCode=${result.exitCode})`); await delay(1000); }
throw new Error("Redis did not become ready in time.");}
async function waitForAdmin(sandbox: Awaited<ReturnType<MicroVM["create"]>>, adminSessionID: string) { log("Waiting for REST proxy on port 3000..."); for (let attempt = 0; attempt < 20; attempt += 1) { const result = await sandbox.exec({ command: 'python3 -c "import urllib.request; urllib.request.urlopen(\\"http://127.0.0.1:3000/healthz\\").read()"', timeoutSeconds: 5, });
if (result.exitCode === 0) { log(`REST proxy ready after ${attempt + 1} attempt(s)`); return; }
log(` attempt ${attempt + 1}/20: not ready yet (exitCode=${result.exitCode}) ${result.stderr.trim()}`); await delay(1000); }
const sessionLog = await sandbox.sessionLog(adminSessionID); console.error("[admin session log]\\n" + new TextDecoder().decode(sessionLog)); throw new Error("The REST proxy did not become ready in time.");}
async function main() { const client = new MicroVM({ apiUrl, patToken });
log(`Creating sandbox (image=redis:7-bookworm cpu=1 mem=1024MB disk=5GB)...`); const sandbox = await client.create({ image: "redis:7-bookworm", osUser: "root", cpu: 1, memoryMB: 1024, diskGB: 5, // Prevent the image's default redis-server from starting so we can // start our own instance with requirepass configured. containerCommand: ["sleep", "infinity"], env: { REDIS_PASSWORD: redisPassword, PUBLIC_AUTH_TOKEN: patToken!, PORT: String(adminPort), }, lifecycle: { stopIfIdleFor: 30 * 60 * 1_000_000_000, destroyIfIdleFor: 6 * 60 * 60 * 1_000_000_000, }, }); log(`Sandbox created: ${sandbox.id}`);
log("Installing python3 and redis-py..."); const aptResult = await sandbox.exec({ command: "apt-get update && apt-get install -y --no-install-recommends python3 python3-redis ca-certificates && rm -rf /var/lib/apt/lists/*", timeoutSeconds: 240, }); if (aptResult.exitCode !== 0) { throw new Error(`apt-get failed (exitCode=${aptResult.exitCode}): ${aptResult.stderr}`); } log("python3 and redis-py installed");
log("Creating workspace directories and uploading admin server script..."); await sandbox.exec("mkdir -p /workspace/admin"); await sandbox.uploadFile("/workspace/admin/redis-admin.py", adminServer); log("Admin script uploaded to /workspace/admin/redis-admin.py");
log("Starting Redis session..."); const redisSession = await sandbox.createSession({ name: "redis", command: `redis-server --requirepass "${redisPassword}" --bind 0.0.0.0`, }); log(`Redis session started: ${redisSession.id}`);
await waitForRedis(sandbox);
log("Starting admin HTTP server session..."); const adminSession = await sandbox.createSession({ name: "redis-admin", command: "python3 /workspace/admin/redis-admin.py", workDir: "/workspace/admin", env: { REDIS_URL: redisURL, PUBLIC_AUTH_USER: publicAuthUser, PUBLIC_AUTH_PASSWORD: publicAuthPassword, PORT: String(adminPort), }, }); log(`Admin session started: ${adminSession.id}`);
await waitForAdmin(sandbox, adminSession.id);
log(`Exposing admin HTTP port ${adminPort}...`); const adminExposure = await sandbox.exposePort(adminPort); log(`Admin port exposed: ${adminExposure.url}`);
log(`Exposing Redis port ${redisPort} via TCP...`); const redisExposure = await sandbox.exposePort(redisPort, { protocol: "tcp" }); log(`Redis TCP endpoint: ${redisExposure.url}`);
const payload = { sandboxID: sandbox.id, redisSessionID: redisSession.id, adminSessionID: adminSession.id, upstashRestUrl: adminExposure.url, upstashToken: patToken, redisEndpoint: redisExposure.url, connectionString: `redis://:${redisPassword}@${redisExposure.url.replace('tcp://', '')}`, };
await writeFile("create-upstash-redis.json", `${JSON.stringify(payload, null, 2)}\n`); log("Written create-upstash-redis.json"); console.log(JSON.stringify(payload, null, 2));}
main().catch((error) => { console.error(error); process.exitCode = 1;});What the script gives you
Section titled “What the script gives you”create-upstash-redis.jsonwith the sandbox ID, the running session IDs, the public Upstash-compatible REST URL and Token, and a nativeredis://...connection string.- A Redis server listening on port
6379inside the sandbox, published as atcp://<public-host>:<host-port>endpoint. - A REST proxy on port
3000that mimics the Upstash REST API.
Connecting via Upstash SDK
Section titled “Connecting via Upstash SDK”The upstashRestUrl and upstashToken returned by the script are compatible with the @upstash/redis library:
import { Redis } from '@upstash/redis'
const redis = new Redis({ url: 'https://...', // your upstashRestUrl token: '...', // your upstashToken})
const data = await redis.set('foo', 'bar')Connecting natively
Section titled “Connecting natively”The redisEndpoint or connectionString returned by the script can be used with any Redis client:
redis-cli -u "redis://:password@<public-host>:<host-port>"