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: Add Vercel Blob Storage

In the Vercel Dashboard, go to StorageCreate DatabaseBlob. Link the Blob store to your backend project. This auto-provisions the BLOB_READ_WRITE_TOKEN environment variable — no manual value needed.

info

Vercel Blob is used for certificate storage, agent binary uploads, and other file-based data that replaces the filesystem on serverless.

Step 4: 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 5: Set Environment Variables

Generate Secrets
./scripts/generate-secrets.sh --target vercel

Outputs SESSION_SECRET, ENCRYPTION_SECRET, CLI_AUTH_SECRET, and Ed25519 signing keys — ready to paste.

Backend

VariableValue
CLERK_PUBLISHABLE_KEYpk_live_...
CLERK_SECRET_KEYsk_live_...
SESSION_SECRETRandom 32+ chars
ENCRYPTION_SECRETRandom 32+ chars (required, no fallback)
CLI_AUTH_SECRETRandom 32+ chars
TURSO_DATABASE_URLlibsql://...turso.io
TURSO_AUTH_TOKENTurso auth token
SIGNING_PRIVATE_KEY_B64From Step 2
SIGNING_PUBLIC_KEY_B64From Step 2
BLOB_READ_WRITE_TOKENAuto-provisioned by Blob integration (Step 3)
CORS_ORIGINhttps://<frontend>.vercel.app
AGENT_SERVER_URLhttps://<backend>.vercel.app
TESTS_REPO_URLTest library Git URL
GITHUB_TOKENPAT with repo scope (only for private repos)
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

Helper Scripts

# Generate all secrets including Ed25519 signing keys
./scripts/generate-secrets.sh --target vercel

# Interactive setup wizard — collects Clerk/ES/Turso config, outputs Vercel-ready env vars
./scripts/setup.sh # Select: PaaS → Vercel

# Initialize Elasticsearch indices on Elastic Cloud
./scripts/init-elasticsearch.sh --cloud-id "deploy:..." --api-key "..."

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.