10월 9일 Hacker News에 "Cloudflare acquires Deno"라는 글이 올라왔는데,
1,000점 넘는 점수에 댓글도 500개 넘게 달렸습니다.
팀이 옮겨 간다는 소식만이 아니라,
런타임(Runtime) 개발과 Deno Deploy 운영을 언제까지 이어 갈지도 함께 발표됐기 때문인데요.
Deno로 서버를 만들거나 Deno Deploy를 쓰고 있다면 그냥 넘기기 어려운 소식입니다.
그런데 Ryan Dahl이 올린 Deno 공식 발표의 표현은,
‘인수’가 아니라 ‘Deno 팀이 Cloudflare에 합류한다(joining)’입니다.
같은 날 Kenton Varda와 Ryan Dahl이 함께 쓴
Cloudflare 발표도 공개됐습니다.
반면 The New Stack 보도는,
Cloudflare가 Deno를 인수한다(acquires)고 전했고,
인수 조건은 공개되지 않았다고 덧붙였습니다.
그래서 이 글에서는 금액 같은 이야기보다,
발표에서 확인되는 일정과 실제 코드에서 달라지는 부분을 직접 돌려 보며 정리해 보겠습니다.
무엇이 발표됐나
Deno라는 이름 아래 있던 프로젝트마다 앞으로의 길이 조금씩 다릅니다.
- Deno 런타임
앞으로 1년간 매월 릴리스로 버그 수정과 보안 업데이트를 제공합니다. 이후 Deno 팀의 런타임 개발은 종료됩니다. MIT 라이선스의 오픈소스로 남으며, 개발을 이어 갈 사람을 환영한다고 밝혔습니다. - Deno Deploy
6개월 더 운영한 뒤 종료됩니다. 유료 고객에게는 Cloudflare Workers로 옮기는 마이그레이션(Migration) 지원을 제공합니다. - JSR
패키지 레지스트리(Package Registry)는 계속 운영하고, 인프라를 Cloudflare로 옮깁니다. - rusty_v8
지원을 계속하며, workerd에 통합하는 방향입니다. - celld+workerd
celld의 코드와 아이디어를 workerd에 합치고, workerd를 직접 운영하는 방식을 정식으로 지원할 계획입니다.
여기서 "오픈소스로 남는다"는 말과 "개발이 계속된다"는 말은 구분해서 봐야 합니다.
코드는 공개된 채로 남지만,
1년 뒤부터 새 버전을 책임지는 팀은 없어지기 때문입니다.
왜 Cloudflare였을까
두 발표를 이해하는 데 중요한 프로젝트는,
Deno가 2026년 8월 공개한 celld입니다.
Cloudflare Workers의 프로그래밍 모델 위에서,
Workers와 Durable Objects를 직접 운영할 수 있게 만든 오픈소스 구현입니다.
셀프호스팅(Self-hosting)은 클라우드 회사에 맡기지 않고 내 서버에서 직접 돌린다는 뜻인데,
celld는 Rust로 만든 바이너리(Binary) 하나에,
오브젝트 스토리지(Object Storage) 하나만 있으면 동작합니다.
Durable Objects는 각각 SQLite 데이터베이스(Database)를 가진,
작은 서버 같은 단위로 이해하면 됩니다.
단일 스레드(Single Thread)로 실행하고 웹소켓(WebSocket)을 처리하며,
Dahl은 채팅 채널마다 하나씩 두면 확장할 수 있다고 설명했습니다.
Cloudflare Workers 런타임의 오픈소스 버전인 workerd는,
실제 운영 환경과 같은 코드를 사용합니다.
하지만 Kenton Varda는 workerd의 Durable Objects가 단일 인스턴스(Instance)만 지원해,
로컬 테스트 수준에 머물고 확장하지 못하는 빈 곳이 있었다고 인정했습니다.
Cloudflare 쪽에서 비어 있던 부분을 celld가 채우고 있었던 셈입니다.
Varda도 셀프호스팅 런타임의 개발 경험은 Deno 팀이 더 잘 안다고 평가했고,
앞으로 Ryan Dahl과 Bert Belder가 이 작업을 이끌 예정입니다.
합쳐진 프로젝트는 오픈소스로 유지할 계획이고,
Dahl은 The New Stack에 Apache 2.0 라이선스가 될 가능성이 높다고 말했습니다(아직 확정은 아님).
실습 환경
같은 타입스크립트(TypeScript) 파일을 Deno와 workerd에서 실행해 비교했습니다.
- 운영 환경
Linux x86_64 - Deno
2.9.7, V8 15.0.245.2-rusty, TypeScript 6.0.3 - wrangler
4.149.0 - workerd
1.20261006.1 - Node.js
v24.21.0
이번 wrangler 버전은 Node.js 22 이상이 필요했습니다.
처음에는 Node 20에서 실행했다가 Wrangler requires at least Node.js v22.0.0 오류로 바로 막혀서,
Node 24를 따로 설치해 사용했습니다.
(Workers 실습인데 첫 관문이 Node 버전이었음...)
버전이나 플랫폼(Platform)이 바뀌면,
예전 설정이 그대로 통하는지 직접 실행해 확인할 필요가 있습니다.
Next.js 16에서 styled-components를 다시 정리한 글과 같은 맥락에서,
이번에도 설치만 하고 끝내지 않고 실제 응답까지 확인했습니다.
Deno 2로 TypeScript 서버 띄우기
먼저 Deno를 설치하고 버전을 확인합니다.
curl -fsSL https://deno.land/install.sh | sh
deno --version
deno 2.9.7 (stable, release, x86_64-unknown-linux-gnu)
v8 15.0.245.2-rusty
typescript 6.0.3
src/index.ts에는 요청을 받아 응답을 돌려주는 핸들러(Handler)를 작성합니다.
/hello는 인사를, /hash는 요청 본문의 SHA-256 해시(Hash)를 돌려주고,
Deno 전용 기능 없이 웹 표준 API(Request, Response, crypto.subtle)만 사용했습니다.
// Deno와 Cloudflare Workers에서 그대로 도는 fetch 핸들러
export default {
async fetch(req: Request): Promise<Response> {
const url = new URL(req.url);
if (url.pathname === "/hello") {
const name = url.searchParams.get("name") ?? "world";
return Response.json({
message: `hello, ${name}`,
runtime: typeof Deno !== "undefined" ? "deno" : "workerd",
});
}
if (url.pathname === "/hash" && req.method === "POST") {
const body = new Uint8Array(await req.arrayBuffer());
const digest = await crypto.subtle.digest("SHA-256", body);
const hex = [...new Uint8Array(digest)]
.map((b) => b.toString(16).padStart(2, "0"))
.join("");
return Response.json({ bytes: body.length, sha256: hex });
}
return new Response("Not Found", { status: 404 });
},
};
deno serve는 export default { fetch } 형태의 파일을,
서버 시작 코드 없이 바로 띄워 줍니다.
이 모양이 Workers의 기본 형태와 같아서 이번 비교에 그대로 쓸 수 있습니다.
deno serve --port 8000 src/index.ts
curl -s "localhost:8000/hello?name=suomokwon"
printf 'hello deno' | curl -s -X POST --data-binary @- localhost:8000/hash
deno serve: Listening on http://0.0.0.0:8000/
{"message":"hello, suomokwon","runtime":"deno"}
{"bytes":10,"sha256":"61d7e61e68f6462c0e56a26ea5c7f73cb2bd3ee25e3349af301a9647a3ef9b2f"}
응답의 runtime 값을 보면 Deno에서 실행됐다는 걸 알 수 있고,
해시 요청은 보낸 10바이트 본문의 SHA-256 값을 돌려줬습니다.
이 해시 값은 다음 단계에서 workerd 결과와 비교하는 기준으로 씁니다.
같은 코드를 wrangler dev로 돌려 보기
이제 같은 src/index.ts를 wrangler에서 실행합니다.
wrangler.jsonc에 실행할 파일과 호환성 날짜(Compatibility Date)를 지정하고,
로컬 개발 서버를 시작했습니다.
{
"name": "deno-vs-workers",
"main": "src/index.ts",
"compatibility_date": "2026-10-01"
}
npm i -D wrangler
npx wrangler dev --port 8787
curl -s "localhost:8787/hello?name=suomokwon"
printf 'hello deno' | curl -s -X POST --data-binary @- localhost:8787/hash
⎔ Starting local server...
[wrangler:info] Ready on http://127.0.0.1:8787
{"message":"hello, suomokwon","runtime":"workerd"}
{"bytes":10,"sha256":"61d7e61e68f6462c0e56a26ea5c7f73cb2bd3ee25e3349af301a9647a3ef9b2f"}
파일을 수정하지 않았는데도 두 요청 모두 처리됐고,
SHA-256 결과도 Deno에서 받은 값과 같았습니다.
별도로 확인한 node:crypto의 createHash도,
호환성 플래그(Compatibility Flag) 없이 양쪽에서 동작했습니다.
다만 이번 실습은 Cloudflare 계정에 로그인하지 않은 상태라,
wrangler deploy로 실제 배포는 하지 않았습니다.
wrangler dev는 Workers의 오픈소스 런타임인 workerd를 로컬에서 띄우기 때문에,
확인한 범위는 이 로컬 workerd의 응답까지입니다.
(실제 Cloudflare 네트워크에서의 동작은 배포해서 따로 확인해야 함)
웹 표준 API만으로 쓴 파일이라 양쪽에서 그대로 돌았던 것이고,
Deno 애플리케이션(Application) 전체가 수정 없이 옮겨진다는 뜻은 아닙니다.
런타임 전용 API가 끼어들면 이야기가 달라지죠.

옮길 때 걸리는 부분: Deno 전용 API
환경 변수(Environment Variable)를 읽는 코드를,
Deno 전용 API인 Deno.env.get으로 바꾸면 결과가 달라집니다.
요청 처리 함수의 모양은 같아도,
그 안에서 사용하는 API가 실행 환경에 묶이기 때문입니다.
// Deno 전용 API를 쓰는 핸들러
export default {
fetch(): Response {
const region = Deno.env.get("REGION") ?? "local";
return Response.json({ region });
},
};
Deno에서는 REGION=seoul deno serve --allow-env로 실행했을 때,
{"region":"seoul"}을 반환했습니다.
같은 코드를 wrangler dev에서 실행하면,
Deno가 정의되지 않았다는 오류와 함께 HTTP 500 응답이 나옵니다.
[wrangler:error] ReferenceError: Deno is not defined
at Object.fetch (file:///.../src/index.ts:4:20)
[wrangler:info] GET / 500 Internal Server Error (21ms)
Workers에서는 환경 값을 fetch의 두 번째 인자인 env로 받습니다.
코드에서는 env를 읽도록 바꾸고,
값은 wrangler.jsonc의 vars에 넣었습니다.
// Workers 방식: 환경 값은 fetch의 두 번째 인자(env)로 받는다
interface Env { REGION?: string }
export default {
fetch(_req: Request, env: Env): Response {
return Response.json({ region: env.REGION ?? "local" });
},
};
{
"name": "env-test",
"main": "src/index.ts",
"compatibility_date": "2026-10-01",
"vars": { "REGION": "seoul" }
}
Your Worker has access to the following bindings:
Binding Resource Mode
env.REGION ("seoul") Environment Variable local
{"region":"seoul"}
수정한 뒤에는 workerd에서도 같은 값을 돌려줬습니다.
(코드 두 줄 차이지만, 이런 부분이 프로젝트 곳곳에 흩어져 있으면 꽤 일이 됨...)
Deno.readTextFile, Deno.Kv처럼 Deno.로 시작하는 전용 API는 workerd에 없으므로,
옮길 코드에서 먼저 찾아볼 대상입니다.
Node, Deno, Workers 개발자에게는 무엇이 달라지나

Deno 개발자라면 보안 업데이트가 나오는 1년 안에,
프로젝트를 어디로 가져갈지 정해야 합니다.
첫 작업은 Deno. 네임스페이스(Namespace)를 쓰는 부분을 찾아내는 것이고,
나머지 코드는 웹 표준 API로 맞춰 두면 옮길 때 고칠 곳이 줄어듭니다.
진입점(Entry Point)을 export default { fetch } 형태로 써 두는 것도 도움이 됩니다.
Deno Deploy 사용자는 더 급합니다.
런타임 지원 기간과 별개로 서비스가 6개월 뒤 종료되니,
유료 고객이라면 Workers 이전 지원부터 확인해 보는 게 좋겠습니다.
Node 개발자에게 당장 바뀌는 것은 없습니다.
다만 Node의 대안으로 고려하던 Deno의 개발 방향은 달라졌습니다.
Deno는 그동안 Node.js 호환을 계속 넓혀 왔는데,
Node 호환이 된다고 해서 모듈 방식이나 런타임 전용 기능까지 그대로 통하는 것은 아닙니다.
이 부분은 ESM과 CommonJS 차이를 정리한 글에서 다룬 함정들과 결이 비슷합니다.
The New Stack은 2025년 12월 Anthropic에 인수된 Bun이 개발을 계속하는 반면,
Deno 팀의 런타임 개발은 종료된다는 차이도 짚었습니다.
Workers 개발자에게는 workerd 셀프호스팅을 정식으로 지원하겠다는 방향이 눈여겨볼 부분입니다.
Durable Objects를 Cloudflare 밖에서도 확장해 쓸 수 있게 만들 계획인데,
아직은 계획 단계라 지금 바로 쓸 수 있는 기능은 아닙니다.
Cloudflare는 몇 달 안에 추가 발표를 하겠다고 했고,
Dahl은 상태를 유지하고 웹소켓을 쓰는 AI 에이전트(Agent) 실행에 Durable Objects가 잘 맞는다고 강조했습니다.
맺으며
이번 발표에서 먼저 챙길 것은 인수냐 합류냐 하는 표현보다,
런타임 지원 1년, Deploy 운영 6개월이라는 남은 기간이 아닐까 싶습니다.
직접 돌려 보니 웹 표준 API로 쓴 작은 서버는 Deno와 workerd에서 그대로 실행됐고,
Deno.로 시작하는 전용 API에서만 손이 갔습니다.
Deno 프로젝트를 유지하거나 Workers로 옮길 계획이 있는 분이라면,
이 두 예제를 출발점으로 내 코드의 Deno. 사용 부분부터 찾아보시길 추천합니다.
Workers를 내 서버에서 돌리고 싶었던 분이라면,
앞으로 나올 workerd 셀프호스팅 발표를 지켜볼 만합니다.