← posts / infrastructure

Deno Deploy shuts down in six months: moving a small app to Cloudflare Workers, self-hosted Deno in Docker, or Node

Deno is joining Cloudflare: Deploy has six months left and the runtime gets a year of fixes. Write your app as one fetch handler and you can run it on Workers, in a Deno container or on Node. Code for all three, plus what does not carry over.

if.codesOct 10, 2026 · 6 min read#deno#cloudflare-workers#migration#dockerAI-assisted

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:

Structuring your application as a single handler makes it portable across different platforms.
Structuring your application as a single handler makes it portable across different platforms.
// 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 second env argument to fetch, not through Deno.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.cron jobs become Cron Triggers: a triggers.crons list in the config and a scheduled handler 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:

Running Deno in a container provides a bridge to self-hosting on your own infrastructure.
Running Deno in a container provides a bridge to self-hosting on your own infrastructure.
// 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.

Need the backend underneath done properly?I build and harden Go and Python backends. The estimate is free.

Exit C: Node

If you want a runtime with no end date in sight, Node is the long-term option. Deno's announcement itself says Node compatibility "became an important part" of the project's work, so many apps already run with few changes. Node's built-in server doesn't take a fetch handler directly; the small @hono/node-server package does the translation:

// server.ts
import { serve } from "@hono/node-server";
import { handler } from "./app.ts";

serve({ fetch: handler, port: 3000 });
console.log("listening on http://localhost:3000");
npm init -y
npm install @hono/node-server
npm install -D tsx
npx tsx server.ts

Rewrite jsr: and URL imports as npm packages (many JSR packages can also be installed through npm). Replace Deno KV with whatever database you'd use anyway, such as SQLite or Postgres, and move cron jobs to the host's scheduler or a job library.

Which one to pick

Choosing the right migration path depends on your long-term maintenance goals.
Choosing the right migration path depends on your long-term maintenance goals.
  • Stateless API, no Deno KV: Workers. The handler moves over almost unchanged, and paying Deploy customers get migration help.
  • Heavy on Deno APIs, deadline pressure: Docker now, then Workers or Node within the patch year.
  • Long-running server, websockets held open, or you want no vendor tie: Node on your own box.

A dated checklist

  1. This week: list every project on Deploy, and note which ones use Deno KV, Deno.cron or custom domains.
  2. This month: export KV data and move routing into a Deno-free handler as shown above.
  3. Before the end of the year: deploy to the new target, point a staging subdomain at it and run the same requests against both.
  4. Before roughly April 2027: switch DNS, then delete the Deploy project once traffic has drained.
  5. Before roughly October 2027: any self-hosted Deno must have moved to a maintained runtime, or you need a plan to patch it yourself.

Sources

if.codesI build RAG, AI integrations and agent pipelines on Go and Python backends — and write about it here.
// keep reading

More posts on AI and backends.

// free quote

Read something you need? I’ll quote it for free.

RAG, AI integrations, agents or the backend underneath — tell me what you have and what should change. I read every request myself.

Free · no commitment

Tell me what you have. I’ll tell you what it takes.

1Describe the projectA few sentences is enough — about two minutes.
2I review itI read it myself and may ask a follow-up question.
3You get a free quoteScope, approach and estimate — yours to keep, no strings.
What kind of project is it?
Free and without obligation. Your details are used only to reply — see the privacy policy.