On 9 October 2026 Ryan Dahl announced that the whole Deno team is joining Cloudflare. Two lines in the post set your deadlines. Deno Deploy "will continue operating for six months before shutting down", with migration help for paying customers moving to Cloudflare Workers. The Deno runtime gets "another year with monthly releases containing bug fixes and security updates", and then development stops. The code stays open source, and JSR keeps running on Cloudflare infrastructure.
The post gives no exact shutdown date. Counting from 9 October, plan for Deploy to be gone around early April 2027 and for runtime patches to end around October 2027. If you have a side project or a small SaaS on Deploy, you have one migration to do and one decision to make: where it runs next.
This guide shows the three realistic exits for a small HTTP app. You write the app once as a plain fetch handler, then add a few lines for each target.
Step 1: make the app a single fetch handler
Most Deploy apps start with Deno.serve(handler). The handler takes a web-standard Request and returns a Response. Workers, Deno and Node (through a small adapter) all accept that shape, so move your routing into its own file that never touches the Deno global:

// app.ts - no Deno.* calls in here
export async function handler(req: Request): Promise<Response> {
const url = new URL(req.url);
if (url.pathname === "/health") {
return new Response("ok");
}
if (url.pathname === "/api/echo" && req.method === "POST") {
const body = await req.json();
return Response.json({ received: body });
}
return new Response("Not found", { status: 404 });
}
Anything that reads environment variables, files or Deno KV goes behind a small interface you pass in, not a direct Deno.env.get(). That is the only refactor most small apps need.
Exit A: Cloudflare Workers
This is the path the announcement points to, and the one with migration support for paying customers. A Worker is a module whose default export has a fetch method:
// worker.ts
import { handler } from "./app.ts";
export default {
fetch(req: Request): Promise<Response> {
return handler(req);
},
};
Add a Wrangler config next to it:
{
"name": "my-deno-app",
"main": "worker.ts",
"compatibility_date": "2026-10-01"
}
Save it as wrangler.jsonc, then run it locally and deploy:
npx wrangler dev
npx wrangler deploy
Cloudflare's docs say that with a compatibility date of 2026-08-04 or later, Node.js compatibility is on by default, so node: imports work without extra flags. For an older date you add "compatibility_flags": ["nodejs_compat"].
What changes on Workers:
- No
Deno.*APIs. Environment variables arrive as the secondenvargument tofetch, not throughDeno.env. - No Deno KV. Pick Workers KV for simple key-value data, D1 for SQL, or Durable Objects for per-key state. The Deno announcement does not mention what happens to KV data on Deploy, so export it yourself early.
- Cron.
Deno.cronjobs become Cron Triggers: atriggers.cronslist in the config and ascheduledhandler on the same default export. - It is not a long-running server. Deno's tutorial "Deploying Deno to Cloudflare Workers" (in Sources) notes you can deploy module workers, not arbitrary web servers. A handler like the one above is fine; anything that keeps sockets or timers open between requests is not.
Exit B: keep Deno, run it yourself in Docker
If your app leans on Deno APIs, the shortest move is to keep the runtime and host it on a VPS or any container platform. You still get a year of patches, and you can switch runtimes later without a deadline. Keep the entry point thin:

// main.ts
import { handler } from "./app.ts";
Deno.serve({ port: 8000 }, handler);
A Dockerfile based on the "Deno and Docker" guide in Deno's docs, running as a non-root user as that guide recommends:
FROM denoland/deno:latest
WORKDIR /app
COPY deno.json deno.lock ./
RUN deno install --frozen
COPY . .
RUN deno cache main.ts
USER deno
EXPOSE 8000
CMD ["deno", "run", "--allow-net", "--allow-env", "main.ts"]
deno install --frozen installs the dependencies in deno.json and fails if the lockfile would change, which is what you want in a build. The official image already contains an unprivileged deno user, so USER deno covers the non-root advice in Deno's guide without creating one yourself.
The guide's example uses latest; for production, replace it with the exact 2.x version tag you tested against (Docker Hub lists them), so a rebuild doesn't silently change the runtime. If you don't have a deno.lock yet, generate one first with deno install.
docker build -t my-deno-app .
docker run --rm -p 8000:8000 my-deno-app
curl http://localhost:8000/health
Narrow the permission flags where you can, as the Deno guide recommends: --allow-net=api.example.com beats a bare --allow-net for outbound calls. Deno KV and Deno.cron still work when self-hosted, but both sit behind unstable flags (--unstable-kv, --unstable-cron), and KV then lives in a local SQLite file you must put on a volume and back up. On Deploy that was the platform's job; now it is yours.
Treat this as a bridge, not a destination. When the patch year ends, an internet-facing runtime with no security fixes is a liability.



