DEPLOY GUIDE · RENDER.COM

Watchword on Render in 5 Minutes

A 4-line Dockerfile in your own repo, a free Turso database, and three secrets — render builds the image for you on the free tier and serves a public HTTPS MCP endpoint Claude.ai, Cursor, and ChatGPT can connect to.

What you get

Public HTTPS MCP endpoint

Render provisions a TLS certificate automatically. Point Claude.ai, Cursor, ChatGPT, or any MCP client at https://watchword-<your>.onrender.com and you're done.

Persistent Turso database

libSQL/Turso replaces SQLite for hosted environments — same schema, same migrations, but the data survives container restarts and scale-to-zero.

Container-native deploy

Render builds your Dockerfile on every push. The image extends ghcr.io/giglabo/watchword:1.6.0 and bakes in your config.yaml. No buildpacks, no registry account, no paid plan.

Production auth out of the box

Static bearer tokens for simple setups; JWT/JWKS validation against Auth0, Keycloak, or a Cloudflare Worker OAuth provider for Claude.ai-grade authorization.

FREE-TIER HACK

Why this works on render's free tier (and image-pulls don't)

Render's free tier doesn't want to pull arbitrary pre-built images for you, but it happily builds a Dockerfile from any public git repo you point it at — for free, on every push. So instead of fighting the platform, give it a 4-line Dockerfile that does FROM upstream + COPY your config. Render builds the resulting image in its own builder and runs it. You get the upstream watchword binary, your config travels with your repo, and the entire pipeline is on the free plan.

"c"># Dockerfile (4 lines, lives in YOUR repo)
"k">FROM ghcr.io/giglabo/watchword:1.6.0
"k">COPY config.yaml /etc/watchword/config.yaml
"k">COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
"k">ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
  • FROM ghcr.io/giglabo/watchword:1.6.0 — render pulls the upstream image during build, not at runtime, so the free tier never has to authenticate against a registry on your behalf.
  • COPY config.yaml — your full config (TTL, JWT issuer, audience, RFC 9728 metadata) lives in your own repo. Tweak it, push, redeploy. No env-var soup, no remote volumes.
  • docker-entrypoint.sh maps render's dynamic $PORT into WORDSTORE_SERVER_HTTP_PORT — the only adapter watchword needs to fit render's contract.
  • Every git push to your repo triggers a fresh build. Pin the upstream tag for stability, or float on `:latest` if you want auto-updates.

Same Dockerfile builds on Fly.io, Railway, and most other Docker-aware PaaS — but in 2026 only Koyeb's Starter plan still matches render's $0 sweet spot. Fly.io dropped its free tier in 2024 (now a $5 trial), and Railway requires a paid plan ($5/mo Hobby minimum) after a 30-day trial. The trick still works there, it just isn't free.

Prerequisites

  • A render.com account (free tier works for testing — see the cost section below)
  • A Turso account and the turso CLI installed (free tier: ~500 MB and 1B row reads)
  • Any git host render can clone from — your own repo with the four files below
  • Optional: a Cloudflare Workers account if you want JWT/OAuth auth instead of static tokens

Deploy in five steps

  1. 01

    Create the Turso database

    Spin up a libSQL database and grab the connection URL plus an auth token. Watchword runs the same migrations against Turso as it does against local SQLite — no schema changes needed.

    "c"># install the "k">turso CLI (macOS)
    "k">brew install tursodatabase/tap/turso
    
    "c"># log in and create the database
    "k">turso auth login
    "k">turso db create watchword
    
    "c"># capture the URL and a token
    "k">turso db show watchword --url           # libsql://watchword-<org>.turso.io
    "k">turso db tokens create watchword        # eyJhbGci...
  2. 02

    Create your deploy repo (4 files)

    Make a small repo on whichever git host you prefer. Drop in these four files — the Dockerfile is the heart of the free-tier hack: it just layers your config on top of the upstream watchword image. No build step on your machine; render's builder does it.

    your-watchword-deploy/
    ├── Dockerfile              # "k">FROM ghcr.io/giglabo/watchword:1.6.0 + "k">COPY config
    ├── config.yaml             # transport, db driver, JWT issuer, RFC 9728 metadata
    ├── docker-entrypoint.sh    # maps render's $PORT to "v">WORDSTORE_SERVER_HTTP_PORT
    └── render.yaml             # 1 service, type: web, runtime: docker

    config.yaml ships transport: http, database.driver: libsql, auth.enabled: true, expiration.ttl_hours: 168. Override anything via WORDSTORE_* env vars in render's dashboard.

  3. 03

    Generate bearer tokens

    Watchword authenticates every MCP request. The simplest mode is a comma-separated list of bearer tokens. Generate them with openssl or any password manager — agents send them as Authorization: Bearer <token>.

    "c"># generate two strong tokens
    openssl rand -hex 32
    openssl rand -hex 32
    
    "c"># you'll paste the comma-joined list into "v">WORDSTORE_AUTH_TOKENS
    "c"># example: 9f4a...,7b22...
  4. 04

    Click 'New Blueprint' on render.com

    In the render.com dashboard, choose New → Blueprint and point it at your repo. Render reads render.yaml, builds your Dockerfile, and prompts you for the three secret env vars below. Set them, click Apply, and the build starts.

    render.yaml declares one web service of type docker, runs your Dockerfile, and exposes the env vars below as sync:false (i.e. you set them per-environment).

  5. 05

    Connect your MCP client

    Once the build is green, copy the .onrender.com URL. Add it to Claude Code, Cursor, or any MCP client as a remote server with the streamable-http transport and your bearer token.

    "c"># Claude Code
    "k">claude mcp add watchword --transport http \
    --header "Authorization: Bearer <your-token>" \
    https://watchword-<your>.onrender.com/mcp
    
    "c"># verify
    "k">claude mcp list

Environment variables

These three secrets live in render.com's environment settings (sync:false in the Blueprint, so they're never committed). The rest of watchword's behavior — TTL, transport, JWT issuer — is baked into config.yaml in your repo.

WORDSTORE_AUTH_TOKENStok1,tok2,...

Comma-separated bearer tokens. Each MCP client gets its own. Rotate by editing this list — old tokens stop working immediately.

WORDSTORE_DATABASE_LIBSQL_URLlibsql://...turso.io

The URL from `turso db show <db> --url`. Watchword talks to it via pure-Go libsql-client-go — no CGO, no native deps.

WORDSTORE_DATABASE_LIBSQL_AUTH_TOKENeyJhbGci...

The token from `turso db tokens create <db>`. Treat it like a database password. Rotate via Turso CLI when needed.

WORDSTORE_AUTH_JWT_AUDIENCEwatchword-mcp

Optional — only if you enabled JWT auth. Matches the `aud` claim issued by your OAuth provider so the same JWKS can serve multiple environments.

Auth: bearer, JWT, or both

The shipped config enables both bearer and JWT side-by-side. Bearer tokens are simple and work everywhere; JWT/JWKS plus RFC 9728 protected resource metadata is what Claude.ai needs for its auto-discovery flow.

  • Static bearer mode — set WORDSTORE_AUTH_TOKENS, point clients at /mcp with Authorization: Bearer <token>. Done.
  • JWT/JWKS mode — point watchword at your OAuth provider's JWKS URL; works with Auth0, Keycloak, Cognito, Cloudflare Access, or a self-hosted Cloudflare Worker OAuth provider.
  • RFC 9728 — config.yaml exposes /.well-known/oauth-protected-resource so Claude.ai discovers the authorization server automatically.
  • Mix and match — both modes can be active simultaneously; bearer tokens stay valid even after JWT is wired up.

Cost breakdown

Watchword on render is cheap. The free tier covers small personal use; production runs comfortably on the Starter plan with a paid Turso plan only if you exceed the free quota.

render.com web service$0–$7/moFree instance type sleeps after 15 min idle. Starter ($7) keeps it warm and adds a custom domain.
Turso libSQL database$0–$29/moFree: ~500 MB / 1B row reads. Scaler ($29) only needed for heavy multi-tenant use.
Bandwidth & TLS$0render.com bundles bandwidth and auto-renews the TLS certificate on every deploy.
Cloudflare Worker (optional)$0Workers free tier covers 100K requests/day — more than enough for OAuth metadata + JWKS endpoints.

FAQ

Why does this work on render's free tier?

Render's free Docker runtime builds your Dockerfile from a public git repo on every push — that's free. What it doesn't do for free is pull arbitrary pre-built private images at runtime. The 4-line Dockerfile sidesteps the registry-pull entirely: render's builder does the pull during build, the resulting image lives on render's side, and runtime never has to authenticate to ghcr.io.

Can I use my own domain?

Yes. Add a custom domain in render.com's service settings, point a CNAME to <service>.onrender.com, and update the resource_metadata.resource value in config.yaml so Claude.ai discovery returns the correct URL. Render handles the TLS cert.

Does the free tier really work?

For solo use, yes. Render's free instance sleeps after 15 min — when an MCP client reconnects, the cold start is ~30 seconds. For always-on use, upgrade to Starter ($7/mo).

Why Turso instead of render's PostgreSQL?

Watchword is SQLite-native; libSQL/Turso runs the exact same migrations and schema with zero changes. Render's managed Postgres works too — set DATABASE_URL and switch the driver in config.yaml — but Turso's free tier is more generous for this workload, and you keep render's free tier intact.

How do I rotate tokens?

Edit WORDSTORE_AUTH_TOKENS in render.com's env settings and trigger a redeploy (or wait for the next git push). Old tokens stop working the moment the new pod boots.

Where do I monitor it?

Render's built-in logs and metrics cover request volume, errors, and CPU. Watchword logs JSON to stdout (level: info), so you can tail the live log stream from the dashboard or pipe it to a third-party log drain.

Ship it

Four files in a repo, three secrets in render, a Turso database — your MCP memory is online and every agent on your laptop can read from it.

Walk me through the steps