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.
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
- 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...
- 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.
- 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...
- 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).
- 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.ioThe 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-mcpOptional — 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.
If you need to wire up production OAuth — Auth0, Keycloak, or the Cloudflare Worker proxy that lets Claude.ai self-register via Dynamic Client Registration — the paid setup guide gives you copy-paste configs for each IdP, the audit-logged identity-claim mapping, and the 27 production-verified setup gotchas (including the 7 Auth0 DCR blockers that aren't in the official docs). Pick the path that matches your stack and skip the trial-and-error.
Get the Watchword Auth Setup GuideCost 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.
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