Skip to main content

Vercel Deployment (Serverless)

Deploy ProjectAchilles to Vercel using the serverless backend (backend-serverless/) with Turso for the database and Vercel Blob for file storage.

Architecture

ProjectRoot DirectoryRuntime
backendbackend-serverless/@vercel/node (serverless)
frontendfrontend/Vite (static SPA)
Different Backend

The Vercel deployment uses backend-serverless/ — a purpose-built fork that replaces SQLite with Turso, filesystem with Vercel Blob, and process scheduling with Vercel Cron.

ConcernDocker backendVercel backend
DatabaseSQLite (better-sqlite3)Turso (@libsql/client)
File storage~/.projectachilles/Vercel Blob
Signing keysFilesystemEnvironment variables (Ed25519)
Test libraryRuntime git syncBuild-time clone
Cron jobssetIntervalVercel Crons

Prerequisites

  • Vercel account — Pro plan recommended (required for Cron jobs)
  • Turso database (free tier sufficient)
  • Clerk application keys
  • Elastic Cloud deployment

Step 1: Create Turso Database

curl -sSfL https://get.tur.so/install.sh | bash
turso auth login
turso db create projectachilles
turso db show projectachilles --url # → libsql://...turso.io
turso db tokens create projectachilles # → eyJhbGci...

Step 2: Generate Ed25519 Signing Keys

openssl genpkey -algorithm Ed25519 -outform DER -out /tmp/ed25519_private.der
SIGNING_PRIVATE_KEY_B64=$(base64 -w0 /tmp/ed25519_private.der)
SIGNING_PUBLIC_KEY_B64=$(openssl pkey -inform DER -in /tmp/ed25519_private.der \
-pubout -outform DER | base64 -w0)
rm /tmp/ed25519_private.der

Step 3: Create Vercel Projects

Create two separate Vercel projects linked to the same GitHub repo:

  • Backend: Root Directory backend-serverless, Framework Preset "Other"
  • Frontend: Root Directory frontend, Framework Preset "Vite"

Step 4: Set Environment Variables

Backend

VariableValue
CLERK_PUBLISHABLE_KEYpk_live_...
CLERK_SECRET_KEYsk_live_...
SESSION_SECRETRandom 32+ chars
ENCRYPTION_SECRETRandom 32+ chars (required, no fallback)
TURSO_DATABASE_URLlibsql://...turso.io
TURSO_AUTH_TOKENTurso auth token
SIGNING_PRIVATE_KEY_B64From Step 2
SIGNING_PUBLIC_KEY_B64From Step 2
CORS_ORIGINhttps://<frontend>.vercel.app
AGENT_SERVER_URLhttps://<backend>.vercel.app
TESTS_REPO_URLTest library Git URL
GITHUB_TOKENPAT with repo scope
ELASTICSEARCH_CLOUD_IDFrom Elastic Cloud
ELASTICSEARCH_API_KEYFrom Elastic Cloud

Frontend

VariableValue
VITE_CLERK_PUBLISHABLE_KEYpk_live_... (must have VITE_ prefix)
VITE_API_URLhttps://<backend>.vercel.app

Cron Jobs

ScheduleEndpointPurpose
Every minute/api/cron/schedulesProcess pending schedules
Every minute/api/cron/auto-rotationRotate agent API keys

Cron jobs require the Pro plan and only run in Production.

What's Not Available

FeatureStatusReason
Go agent builds503No Go toolchain
Certificate generation503No openssl binary
Runtime git syncDisabledNo persistent filesystem
Certificate uploadAvailableStored in Vercel Blob
Agent binary uploadAvailableStored in Vercel Blob

Gotchas

Always Use printf for Environment Variables

echo appends a trailing newline that corrupts env var values:

# Correct
printf "libsql://..." | vercel env add TURSO_DATABASE_URL production

# Wrong — echo adds \n
echo "libsql://..." | vercel env add TURSO_DATABASE_URL production

Symptoms: TURSO_DATABASE_URL newline → "Invalid URL" on all database endpoints. CORS_ORIGIN newline → HTTP 500 on cross-origin requests.

Frontend Clerk Key Prefix

Vite only exposes env vars with VITE_ prefix. Use VITE_CLERK_PUBLISHABLE_KEY on the frontend project, not CLERK_PUBLISHABLE_KEY.

__dirname is Unreliable

@vercel/node bundles with ncc/esbuild. Use process.cwd() (always /var/task) instead of __dirname for path resolution.

Cost Estimate

ServiceEst. Monthly Cost
Vercel Pro (both projects)$20
Turso (free tier)$0
Vercel Blob (1 GB included)$0
Total~$20

Troubleshooting

CORS errors

Verify CORS_ORIGIN matches the frontend URL exactly (with https://, no trailing slash, no trailing newline).

Agent endpoints return 500

Check TURSO_DATABASE_URL for trailing newline — use printf not echo when setting via CLI.

Test library empty

Tests are cloned at build time. Trigger a redeploy after setting TESTS_REPO_URL and GITHUB_TOKEN.

Cron jobs not running

Verify Pro plan and check the Crons tab in the Vercel Dashboard. Crons only run in Production.