Sink is the Cloudflare-native member of the URL-shortener series.
It is not like YOURLS, Shlink, Kutt, or Snapp, where the natural first thought is “give me the Docker compose.” It is also not like ha.mr, where the whole app is a static browser-side URL compressor.
Sink sits in a different place: you deploy your own instance, but the runtime is Cloudflare Workers and the storage layer is Cloudflare D1 plus KV.
What is Sink?
Sink is an open-source URL shortener designed to run entirely on Cloudflare.
I inspected:
repo: https://github.com/miantiao-me/Sink
commit: bafa4e1f7e3ec034929e11e5542c4c3e116d69da
tag: v0.3.1
license: AGPL-3.0-only
What You Get
Sink covers the normal short-link workflow:
- create and edit short links;
- custom slugs;
- optional case-sensitive slugs;
- UTM parameters;
- expiration dates;
- password-protected links;
- unsafe-link warning pages;
- country and device based routing;
- social preview metadata;
- QR codes;
- JSON import/export;
- CSV analytics export;
- API access;
- MCP endpoint for tool-based management.
It also has optional Workers AI helpers for generating slugs and social metadata.
Architecture
The current Sink stack is:
- Nuxt 4 for the application.
- Nitro Cloudflare preset for deployment to Cloudflare.
- Cloudflare Workers for the app, API, and redirects.
- Cloudflare D1 as the authoritative link database.
- Cloudflare Workers KV as the fast redirect cache and legacy storage bridge.
- Drizzle ORM for the D1 schema and queries.
- Cloudflare Analytics Engine, optionally, for click analytics.
- Cloudflare R2, optionally, for JSON backups and generated social preview images.
- Cloudflare Workers AI, optionally, for AI slug and metadata suggestions.
The app config sets:
ssr: false
So the dashboard behaves like a client-side Nuxt app, while the server/API/redirect logic runs through Nitro on Cloudflare.
The Redirect Path
The redirect path is optimized for Cloudflare.
The practical flow is:
Visitor opens /slug
-> Worker middleware parses the slug
-> KV cache lookup
-> D1 fallback when the cache misses
-> KV write-through cache refresh
-> password / warning / device / country / social-bot rules
-> optional Analytics Engine event
-> redirect response
D1 is the source of truth. KV is the fast read path. That split is important: KV keeps hot redirects fast, while D1 gives the app a structured relational store for links, tags, tombstones, migrations, and metadata.
Required Cloudflare Services
This is the main thing to understand before deploying Sink.
Sink is not a generic “bring any server” Node app. The current repository expects Cloudflare bindings.
Required pieces:
- Cloudflare Workers: runs the app, API, and redirect handler.
- Cloudflare D1: stores link records.
- Cloudflare KV: stores the redirect cache and migration/storage state.
NUXT_SITE_TOKEN: protects the dashboard and API.
Recommended or optional pieces:
- Analytics Engine: click stats and dashboard analytics.
- R2: backups and generated social preview images.
- Workers AI: AI-assisted slugs and metadata.
- Cloudflare account ID/API token: needed for analytics reads and deployment automation.
Here is the dependency map in one place:
| Dependency | Required? | What Sink Uses It For |
|---|---|---|
| Cloudflare Workers | Yes | Runs the Nuxt/Nitro server, API routes, dashboard serving, redirect middleware, and scheduled cron tasks. |
| Cloudflare D1 | Yes | Authoritative relational database for links, tags, tombstones, migrations, expiration metadata, routing rules, passwords, and social metadata. |
| Cloudflare KV | Yes | Fast read-through redirect cache, storage readiness state, and bridge for older KV-backed data. |
NUXT_SITE_TOKEN |
Yes | Bearer token for dashboard/API/MCP access. Without a stable token, you can lock yourself out or get unstable auth behavior. |
| Cloudflare Analytics Engine | Optional | Click-event ingestion and analytics dashboards. Redirects still work without it, but click charts are limited or empty. |
| Cloudflare R2 | Optional | JSON link backups and generated social preview image storage. |
| Cloudflare Workers AI | Optional | AI-assisted slug and OpenGraph metadata generation. Core shortening does not depend on it. |
| Cloudflare account ID/API token | Conditional | Needed for analytics reads and some automated deployment/management flows. |
| Cloudflare custom domain/DNS | Optional but practical | Gives the shortener a real branded domain instead of only a Workers route. |
Is It Self-Hosted?
Yes, but it is Cloudflare self-hosting.
You control the fork, deployment, Worker, secrets, bindings, custom domain, and data resources. You do not control the underlying runtime in the same way you would with a VPS and Docker.
That makes Sink much lighter than Dub, because it stays inside one cloud provider and avoids PlanetScale, Upstash, Tinybird, Vercel, and S3 wiring. But it is less portable than Shlink, YOURLS, Kutt, or Snapp.
Deployment Notes
The current docs recommend Cloudflare Workers. Cloudflare Pages support still exists, but it is deprecated in the project docs.
The deployment shape is:
- Fork the Sink repo.
- Create a D1 database.
- Create a KV namespace.
- Optionally create Analytics Engine, R2, and Workers AI bindings.
- Set build-time resource IDs such as
DEPLOY_D1_DATABASE_IDandDEPLOY_KV_NAMESPACE_ID. - Set runtime secrets such as
NUXT_SITE_TOKEN. - Build with:
pnpm build
- Deploy to Workers with:
pnpm deploy:worker
The deploy script runs the remote D1 migrations and then publishes the Worker with static assets.
One practical footgun: set NUXT_SITE_TOKEN yourself. If you do not, the app can generate a random token, which is not what you want for a stable deployment.
Another practical step: after deploying, open the dashboard’s Links page once. The docs and storage code call out a one-time storage setup path; link creation and backup paths can fail with HTTP 423 until storage setup is complete.
Why There Is No Home-Lab Compose
I found an old Home-Lab Sink note, but it pointed at the previous repo and implied a Cloudflare-only deployment.
That is still the right conclusion, but the details changed:
- current source is
miantiao-me/Sink; - current recommended target is Cloudflare Workers;
- D1 is now required as the authoritative database;
- KV is required as the redirect cache;
- Analytics Engine, R2, and Workers AI are optional extras.
So I updated the Home-Lab note instead of adding a Docker compose file.
If you want the same project family with a local Node/Docker shape, look at the sibling project:
Local Field Test
I cloned the repo locally and inspected the deployment code, middleware, storage layer, docs, and Cloudflare config.
I did not run a full local smoke test for two reasons:
- the repository requires Node
>=24, while this machine currently has Nodev18.19.1; - a meaningful redirect test needs Cloudflare D1/KV bindings, and I did not create new Cloudflare resources for this post.
That is different from the local Docker-based shorteners in this series, where a disposable compose stack can prove the app boot path.
For Sink, the important validation is the Worker deployment and bindings.
Compared with the Other Shorteners
Dub is much heavier. It is a cloud-native SaaS platform with MySQL/PlanetScale, Redis/QStash, Tinybird/ClickHouse, object storage, Vercel-style hosting, and custom-domain workflows.
Shlink is more traditional self-hosting. It runs as a backend service with a separate web client and optional companion services.
Kutt is a conventional Node app. It can run with SQLite, Postgres, MySQL/MariaDB, and optional Redis.
YOURLS is the classic PHP/MySQL path.
Snapp is a focused SvelteKit/Postgres app.
ha.mr is the most different. It has no backend database at all because it compresses the destination into the link.
Sink is best when you already like Cloudflare and want a shortener that lives close to the edge with minimal server maintenance.
FAQ
Can I deploy Sink statically?
No, not with full functionality.
The dashboard frontend is client-side Nuxt, but the useful parts need the Worker/API layer plus D1 and KV bindings. A purely static host cannot create links, read/write D1, update the KV cache, protect the API token, or record Analytics Engine events.
Does Sink require D1 and KV?
Yes.
D1 is the authoritative link store. KV is the redirect cache and storage-state layer. Analytics Engine, R2, and Workers AI are optional; D1 and KV are not.
What is Sink’s stack?
The stack is Nuxt 4 plus Nitro on Cloudflare Workers, with Drizzle ORM talking to Cloudflare D1 and KV used as the hot redirect cache.
The frontend dashboard is a client-side Nuxt app. The backend behavior is the Worker/Nitro side: redirects, API routes, auth checks, storage setup, AI helpers, analytics logging, backups, OpenAPI docs, and MCP endpoint.
What exactly are the Cloudflare dependencies?
The minimum functional deployment needs Cloudflare Workers, D1, KV, and a stable NUXT_SITE_TOKEN.
Analytics Engine, R2, Workers AI, account API tokens, and custom domains are add-ons. They unlock analytics charts, backup/image storage, AI suggestions, API-driven analytics reads, and branded short domains, respectively.
Can I run Sink on a VPS with Docker?
Not this repository in its intended deployment mode.
Current Sink is built around Cloudflare Workers bindings. For a local server or Docker deployment, use Slite from the same project family, or choose YOURLS, Shlink, Kutt, or Snapp.
Are the extra services open source and self-hostable?
The Cloudflare products themselves are managed services, not self-hostable software packages.
D1 uses SQLite concepts underneath, but Cloudflare D1 is a managed Cloudflare database. KV, Analytics Engine, R2, and Workers AI are also managed Cloudflare services.
That is the tradeoff: very little server maintenance, but strong coupling to Cloudflare.
What happens without Analytics Engine?
Redirects and link management can still work, but analytics dashboards will be limited or empty.
Analytics Engine is the click-event store. Without the ANALYTICS binding and the account/API permissions needed to read it, you should not expect full click charts.
What happens without R2?
You lose the R2-backed backup and generated social-image storage paths.
The backup docs describe JSON snapshots of link data, not a full database dump. Those snapshots may contain sensitive link data, so treat the bucket like production data.
What happens without Workers AI?
AI helper endpoints return unavailable behavior instead of generating suggestions.
This does not break the core shortener. If enabled, remember that AI-assisted slug or metadata generation can send page content and destination URLs to Cloudflare Workers AI.
Is Cloudflare Pages still supported?
The repo still has Pages-related scripts and docs, but Workers is the recommended deployment path and Pages is marked deprecated in the current docs.
For a new deployment, use Workers unless you have a specific reason to maintain a Pages setup.
Does Sink have an API?
Yes.
Every instance exposes OpenAPI/Scalar/Swagger documentation paths, and API calls authenticate with:
Authorization: Bearer YOUR_SITE_TOKEN
The project also exposes an MCP endpoint at:
/api/mcp
That is useful if you want tools or agents to manage links through the same authenticated surface.
Should I choose Sink or Dub?
Choose Sink if your short-link stack can live entirely in Cloudflare and you want less operational sprawl.
Choose Dub if you want a full link-attribution SaaS platform with deeper marketing analytics, conversion tracking, partner workflows, and the operational cost that comes with that architecture.
Comments