RE:NODE

Bun или Deno на небольшом сервере: какой runtime выбрать

Автор Команда RE:NODE
19 мин чтения


Для небольшого приложения на небольшом сервере Node.js по-прежнему остаётся безопасным выбором по умолчанию, Bun стоит попробовать, когда нужен кратчайший путь от файла TypeScript до работающего HTTP-сервера, а Deno стоит взять, когда вы хотите, чтобы runtime отказывался делать то, о чём вы его не просили. Все три запускают программу одного и того же типа: долгоживущий процесс, который слушает порт и отвечает по HTTP. Различия, важные на хостинговом тарифе, невелики и практичны: как запускается сервер, как устанавливаются зависимости, как обрабатывается TypeScript, как называется lockfile и какие пакеты из npm тихо перестают работать. В этой статье разобрано каждое из них на реальных командах, а в конце - как выбрать и что проверить, если приложение запустилось, но ничего не отвечает.

Цифр из бенчмарков здесь нет - намеренно. Показатели пропускной способности и памяти у runtime зависят от версии, нагрузки и машины, а числа в большинстве сравнительных статей получены на обработчике hello-world, на который не похоже ни одно настоящее приложение. Если важна память, совет один: измерьте собственное приложение на собственном тарифе.

Что представляет собой каждый runtime#

Node.js - признанный лидер. Он выполняет JavaScript на движке V8, поставляется с npm и имеет самое большое количество учебников, поддержки со стороны хостингов и проверенных временем библиотек. TypeScript по умолчанию напрямую не запускается: его компилируют либо используют загрузчик или флаг, который вырезает типы, а то, что поддерживается, зависит от версии Node.

Bun - один исполняемый файл, который одновременно является runtime, менеджером пакетов, средством запуска тестов и сборщиком. Он использует движок JavaScriptCore, а не V8, стремится к совместимости с API Node и с пакетами npm и запускает файлы .ts и .tsx напрямую. Собственный серверный API - Bun.serve. Менеджер пакетов - bun install, а lockfile называется bun.lock.

Deno - runtime, построенный вокруг модели безопасности и веб-стандартных API. Он напрямую запускает TypeScript, поставляется с форматтером, линтером, средством запуска тестов и проверкой типов и выполняет код без доступа к сети, окружению и файловой системе, пока вы сами их не выдадите. Серверный API - Deno.serve. Конфигурация лежит в deno.json, там же определяются задачи.

Сначала нужно уяснить, что это не три вариации одного и того же. У них общий язык и значительная часть стандартного веб-API - fetch, Request, Response, URL, AbortSignal, потоки, - поэтому небольшой обработчик часто можно написать один раз и переносить между ними. Дальше пути расходятся, и именно на расхождениях уходит время.

RuntimeДвижокTypeScript по умолчанию
Node.jsV8Сначала компиляция или флаг, зависящий от версии
BunJavaScriptCoreЗапускает файлы .ts напрямую
DenoV8Запускает файлы .ts напрямую, проверка типов по желанию

Прежде чем полагаться на любое поведение из этой статьи, проверьте, какая версия у вас на самом деле. Все три развиваются быстро, а node --version, bun --version и deno --version - первые строки любого отчёта об ошибке.

Сервер на Bun#

Самый маленький полезный сервер на Bun - это один файл:

server.ts
const port = Number(process.env.PORT) || 3000;const server = Bun.serve({  port,  hostname: "0.0.0.0",  fetch(req) {    const url = new URL(req.url);    if (url.pathname === "/healthz") {      return Response.json({ ok: true, uptime: process.uptime() });    }    return new Response("hello from bun\n");  },});console.log(`listening on ${server.port}`);

Запустите его командой bun run server.ts или просто bun server.ts. Обработчик принимает стандартный Request и возвращает стандартный Response, так что устанавливать фреймворк не нужно. Маршрутизацию вы пишете сами: для нескольких эндпоинтов это нормально, а дальше становится утомительно. Фреймворки на основе веб-стандартных запросов, такие как Hono и Elysia, работают на Bun и обычно становятся следующим шагом. Express тоже работает на Bun через слой совместимости с Node, с оговорками из раздела о совместимости ниже.

Стоит знать три детали. Во-первых, hostname: "0.0.0.0" здесь указан для ясности; суть в том, что сервер должен слушать все интерфейсы, потому что сервер, привязанный к loopback внутри контейнера, недоступен для прокси перед ним. Во-вторых, Bun автоматически читает файл .env из рабочего каталога, а переменные доступны и через process.env, и через Bun.env. Локально это удобно, а на сервере это ловушка: случайный .env, попавший в репозиторий, тихо переопределит ваши намерения или раскроет секрет. Храните настоящие секреты в окружении. В-третьих, bun --hot server.ts перезагружает код при изменении во время разработки, и запускать так в production не следует.

Зависимости ставятся командой bun install. Она читает package.json, создаёт node_modules и записывает дерево в bun.lock. Старые версии Bun писали двоичный файл bun.lockb; современные пишут текстовый bun.lock, который нормально отображается в diff pull request. Если в вашем репозитории лежит старый bun.lockb, прежде чем удалять любой из файлов, проверьте, какой читает ваша версия Bun. Для deploy аналог npm ci выглядит так:

bash
$ bun install --frozen-lockfile

Команда ставит ровно то, что записано в lockfile, и завершается ошибкой, если package.json и lockfile расходятся, - по той же причине, по которой существует npm ci. Добавьте --production, чтобы пропустить devDependencies, если в пути запуска они не нужны.

Сервер на Deno#

Та же программа на Deno:

main.ts
const port = Number(Deno.env.get("PORT")) || 8000;Deno.serve({ port, hostname: "0.0.0.0" }, (req: Request) => {  const url = new URL(req.url);  if (url.pathname === "/healthz") {    return Response.json({ ok: true });  }  return new Response("hello from deno\n");});

Deno.serve принимает объект параметров и обработчик, а обработчик снова представляет собой функцию из Request в Response. Порт по умолчанию - 8000, если вы его не передали, а имя хоста по умолчанию слушает все интерфейсы, но указать оба значения ничего не стоит, и при поиске неисправностей одной догадкой станет меньше.

Больше всего Deno отличается при запуске, из-за разрешений:

bash
$ deno run --allow-net --allow-env=PORT main.ts

Без --allow-net программе отказывают в тот момент, когда она пытается открыть сокет. Без --allow-env вызов Deno.env.get завершается ошибкой разрешения (или задаёт вопрос, если вы запускаете его интерактивно). Модель - запрет по умолчанию, и для небольшого сервера она по-настоящему полезна: зависимость, которой вдруг понадобилось прочитать ваше окружение или куда-то подключиться, останавливается с ошибкой, а не тихо добивается своего.

Конфигурация проекта лежит в deno.json, и на хостинге вам пригодится раздел tasks:

deno.json
{  "tasks": {    "start": "deno run --allow-net --allow-env=PORT,DATABASE_URL main.ts",    "dev": "deno run --watch --allow-net --allow-env main.ts",    "check": "deno check main.ts"  },  "imports": {    "hono": "npm:hono@4"  }}

deno task start выполняет команду из задачи. Блок imports - это import map: он задаёт пакетам короткие имена, чтобы в коде можно было написать import { Hono } from "hono". Префикс npm: берёт пакет из реестра npm, а jsr: - из JSR, собственного реестра Deno. Deno записывает файл deno.lock, фиксируя то, что скачал, а флаг --frozen заставляет установку или запуск завершиться ошибкой, если lockfile пришлось бы изменить. Если в проекте на Deno есть package.json, современные версии читают его и могут устанавливать пакеты в node_modules; это путь для случая, когда вы переносите существующий проект на Node, а не начинаете с нуля.

Флаги разрешений и что выдавать#

Разрешения - главная причина выбрать Deno, и пользоваться ими стоит как следует, а не хвататься за флаг «разрешить всё».

ФлагЧто разрешаетУзкая форма
--allow-netДоступ к сети--allow-net=0.0.0.0:8000,api.example.com
--allow-envЧтение переменных окружения--allow-env=PORT,DATABASE_URL
--allow-readЧтение файлов--allow-read=./static,./data
--allow-writeЗапись файлов--allow-write=./data

Есть и другие (--allow-run для запуска процессов, --allow-ffi, --allow-sys), а -A или --allow-all включает всё сразу. Для сервера, который слушает порт, читает две переменные окружения и отдаёт каталог, честный набор - это первые три флага из таблицы, каждый в узкой форме:

bash
$ deno run \    --allow-net=0.0.0.0:8000,db.internal:5432 \    --allow-env=PORT,DATABASE_URL \    --allow-read=./static \    main.ts

Две оговорки. Узкие формы хороши лишь настолько, насколько хорош ваш список: библиотеке, добавленной в прошлом месяце, может понадобиться ещё одна переменная окружения, и ошибка разрешения проявится не при запуске, а при первом запросе, который до неё доберётся. Прежде чем доверять списку, прогоните приложение по всем реальным путям. А -A - вполне законный выбор для частного инструмента, но тогда вы отказались от возможности, ради которой выбирали этот runtime, так что будьте с собой честны.

В Bun и Node подобного переключателя нет. В новых версиях Node есть экспериментальная модель разрешений за флагом, и её статус меняется от релиза к релизу, поэтому прежде чем на неё опираться, прочитайте документацию к той версии, которую запускаете.

TypeScript без этапа сборки#

Тихая привлекательность и Bun, и Deno в том, что main.ts запускается как есть. Нет tsc, нет каталога dist, нет source maps, которые нужно держать в согласии с работающим процессом, и нет devDependency на компилятор, что обходит ловушку tsc: not found, описанную в статье про npm ci. Для небольшого приложения это убирает из deploy целый этап.

В каждом из них получается немного по-разному:

  • Bun транспилирует TypeScript, вырезая и преобразуя типы, и не проверяет их. Ошибка типов не останавливает программу; её просто не замечают. Если нужны проверки, запускайте tsc --noEmit в CI.
  • Deno для deno run тоже по умолчанию запускает файл, не настаивая на проверке, и предлагает deno check для явной проверки типов. Если добавить deno check main.ts в CI или в задачу, у вас появится страховка.
  • Node в свежих версиях умеет запускать TypeScript, вырезая типы, с ограничениями на поддерживаемые возможности TypeScript. Если вы на это рассчитываете, зафиксируйте версию Node и прочитайте заметки к ней.

Практическое правило везде одно: запустить TypeScript - не значит проверить его. Runtime с удовольствием запустит программу с ошибкой типов, и узнаете вы об этом по TypeError в логе во время работы. Проверяйте типы в CI или перед push и считайте сервер местом, где программа работает, а не где её проверяют.

Есть ещё один подвох. Возможности TypeScript, требующие генерации кода, - например, устаревшие декораторы и некоторые применения enum и namespace - разные инструменты обрабатывают по-разному. Параметры tsconfig.json, такие как experimentalDecorators или paths, Bun читает; Deno берёт параметры компилятора из deno.json и по-своему смотрит на разрешение модулей, в том числе требует явных расширений файлов в относительных импортах. Проект, написанный для сборщика, который разрешает ./utils без расширения, на Deno не заработает, пока в импортах не будет написано ./utils.ts.

Совместимость с npm и что ломается#

Большая часть npm работает на обоих. Пакеты на чистом JavaScript почти всегда, как и популярные фреймворки. Сбои сосредоточены в нескольких местах, и о них стоит знать до переноса существующего проекта.

  • Нативные аддоны. Пакеты, которые при установке компилируют C или C++ и загружают двоичный файл .node, - например, некоторые драйверы для изображений, криптографии и баз данных - зависят от интерфейса нативных аддонов Node. Bun поддерживает Node-API для многих из них, а Deno поддерживает его за разрешением, но пакет, который лезет во внутренности V8, не загрузится. Предпочитайте пакеты, поставляемые с WebAssembly или имеющие запасной вариант на чистом JavaScript.
  • Скрипты установки. bun install по умолчанию не выполняет lifecycle-скрипты произвольных зависимостей; он ведёт список доверенных пакетов, а вы пополняете его через trustedDependencies в package.json. Пакет, которому нужен postinstall, чтобы скачать двоичный файл (движки Prisma, браузер Puppeteer, платформенная сборка sharp в некоторых конфигурациях), может прийти без нужного ему файла. Deno тоже требует явного согласия на lifecycle-скрипты - флагом --allow-scripts при установке.
  • Встроенные модули Node по краям. Оба runtime реализуют большую часть node:fs, node:http, node:crypto, node:stream и других и стремятся повторять поведение Node. Пробелы - в редко используемых уголках: node:cluster, детали node:vm, часть поведения worker_threads, node:inspector и точные тайминги потоков и событий. Код, зависящий от тонкостей поведения, - то место, где перенос идёт не так.
  • Фреймворки, предполагающие серверный объект Node. Express на Bun работает через слой совместимости node:http. Express на Deno работает через npm:express с тем же слоем и правильными разрешениями. Оба пути работают, но вы запускаете API Node на runtime, который не является Node, и ошибка, которая появится, будет на стыке. Фреймворк, построенный на веб-стандартных Request и Response, этого стыка вовсе избегает.
  • Инструменты, ожидающие Node. Всё, что вызывает node через оболочку, читает process.versions.node для ветвления или описано только для npm run и npx, нужно проверить. bunx и deno run npm:package заменяют npx.

Способ выяснить это скучный: установите, запустите приложение, вызовите каждый маршрут, прогоните набор тестов под новым runtime и прочитайте лог на предмет предупреждений. Пакет, «поддерживаемый» по журналу изменений, всё равно может сломаться в том единственном участке кода, который вы используете. Если проект в основном представляет собой кучу зависимостей, привязанных к Node, стоимость переезда обычно перевешивает выгоду.

Память, запуск и вопрос малого тарифа#

Люди переходят на Bun или Deno в надежде на меньшее потребление памяти или более быстрый запуск. Иногда получают, иногда нет. Приводить здесь готовое число было бы нечестно, поэтому скажем то, что можно утверждать наверняка.

Резидентная память зависит от базового уровня движка, объёма загруженного кода, размера ваших данных в памяти, повадок сборщика мусора и аллокатора. Два runtime с одним и тем же приложением могут различаться в любую сторону, а показатель в простое редко предсказывает показатель под нагрузкой. JavaScriptCore и V8 собирают мусор по-разному, и процесс, выглядящий скромно в простое, всё равно может расти при определённом шаблоне запросов. Ни один движок не гарантирует меньшего потребления.

Можно измерить именно то, что вы будете запускать:

  1. Запустите приложение на тарифе, который собираетесь купить, с реалистичной копией данных.
  2. Следите за графиком памяти в консоли, пока воспроизводите типичный трафик, включая самый тяжёлый запрос, который обрабатывает ваше приложение.
  3. Оставьте его работать на сутки. Утечка выглядит как линия, которая никогда не возвращается вниз.
  4. Сравнивайте с лимитом тарифа, а не с числом из блога.

На RE:NODE лимиты памяти работают определённым образом: контейнер, достигший лимита, останавливается и перезапускается начисто, а не уходит в swap. Последствие на Bun и Deno такое же, как на Node: утечка проявляется перезапуском каждые несколько часов. Три перезапуска за час вызывают предупреждение на странице сервера и автоматически открывают тикет. В статье лимиты памяти Node простыми словами разбираются флаги кучи в V8; на Bun они не переносятся, у него свои настройки, так что не вставляйте флаги Node в команду запуска Bun в расчёте на то, что их учтут.

Время запуска - другое заявленное преимущество. TypeScript-файл, который запускается напрямую, экономит этап компиляции, а установщик Bun быстр, но на хостинговом тарифе важно, сколько времени проходит от push до ответа приложения, и это время определяется установкой зависимостей и вашей собственной работой при старте, такой как подключения к базе данных и миграции. Измеряйте deploy, а не hello-world.

Процесс, окружение и порт#

Для всех трёх runtime действуют одни и те же правила, и именно они порождают большинство обращений вида «локально работает».

Читайте порт из окружения. Если порт зашит в код, приложение привязывается туда, куда ничего не маршрутизируется. На RE:NODE у тарифа для приложений одно выделение порта, оно показано на вкладке Network, а переменные окружения задаются на вкладке Startup. Используйте то имя переменной, которое показывает ваш тариф, а не угадывайте, и подставляйте значение по умолчанию только для локальных запусков.

Слушайте все интерфейсы. 0.0.0.0 в Bun.serve и Deno.serve и эквивалент в любом фреймворке. Сервер, который пишет «listening» на 127.0.0.1 и ничего не отвечает, - самый частый сбой, и он даёт connection refused от прокси без единой строки в вашем собственном логе.

Быстро падайте при отсутствии конфигурации. Шаблон из статьи про Express в production переносится без изменений:

javascript
const required = ["DATABASE_URL", "SESSION_SECRET"];const missing = required.filter((name) => !process.env[name]);if (missing.length) {  console.error(`missing configuration: ${missing.join(", ")}`);  process.exit(1);}

В Deno замените process.env[name] на Deno.env.get(name) и помните, что список --allow-env должен включать каждое читаемое имя. Новые версии Deno поддерживают и process.env через слой совместимости с Node, но чище выбрать один стиль на проект. О том, где хранить секреты, говорится в статье переменные окружения и секреты.

Обрабатывайте SIGTERM. Перезапуск или deploy посылают сигнал завершения, а сервер, который его игнорирует, по истечении льготного периода убивают, и запросы в процессе обработки теряются. В Bun и Node работает process.on("SIGTERM", ...), а в Bun приём соединений прекращается вызовом server.stop(). В Deno используйте Deno.addSignalListener("SIGTERM", ...) и server.shutdown() на объекте, который возвращает Deno.serve. Общая схема, включая эндпоинты проверки состояния, описана в статье плавное завершение и проверки состояния.

Пишите логи в stdout. Консоль показывает его без фильтров и в реальном времени. Структурированные строки JSON стоят лишних пяти минут.

Запуск на RE:NODE#

На RE:NODE в категории приложений есть отдельные линейки Bun и Deno, каждая стоит от $4 в месяц за 1 ГБ памяти, 5 ГБ диска и половину vCPU, а выше идут более крупные тарифы. Они стоят рядом с Node.js, Python, Go, Java, Laravel и C#, и панель у всех одинакова: консоль, файловый менеджер, SFTP, резервные копии и вкладка Startup. Тарифы и актуальные цены - на странице хостинга приложений.

Для этих двух линеек есть несколько особенностей:

  • Стартовая страница. Каждая линейка runtime, кроме Node.js, запускает новый сервер с небольшой рабочей страницей по его адресу, пока вы не выложите собственный код, и никогда не перезаписывает ваши файлы. Это быстрый способ убедиться, что адрес и выделение порта работают, ещё до участия вашего кода. На серверах Node.js такой страницы нет.
  • Deploy из GitHub. Функция Git deploy доступна на всех линейках приложений: только GitHub, через GitHub App с короткоживущими токенами, поэтому приватные репозитории работают. Управляют ею два переключателя - pull при каждом запуске и deploy при push, который перезапускает работающий сервер и запускает остановленный, - а каждый deploy оставляет запись. Пошаговое описание для Node в статье deploy приложения на Node.js из GitHub во многом подходит и для остальных линеек.
  • Слот прокси. Тарифы для приложений включают слот прокси. Направьте A-запись на показанный адрес, и сертификат будет выпущен и продлеваться автоматически в 21-дневном окне; настоящий адрес клиента приходит в X-Forwarded-For. В Bun.serve и Deno.serve настройки trust proxy нет, поэтому читайте заголовок сами и, если доверяете одному промежуточному узлу, берите последнюю запись. Подробности - в статьях что делает обратный прокси и как направить домен на сервер.
  • Менеджер процессов не нужен. Панель перезапускает процесс, который завершился, так что PM2 или supervisor не требуются. Честное сравнение - в статье PM2 или панель хостинга.
  • Слоты баз данных. Тарифы для приложений включают два слота баз данных, которые создаются в панели с сгенерированными учётными данными, а вы передаёте их приложению через переменные окружения.

Не стоит предполагать то, о чём панель не говорит. Эта статья не сообщает точную команду запуска нового сервера Bun или Deno и не называет переменную, в которой передаётся порт, потому что их стоит прочитать на собственной вкладке Startup. Считайте страницу hello-world подтверждением адреса, а затем замените её.

Команды установки и запуска для типичного deploy коротки:

bash
# Bunbun install --frozen-lockfile && bun run server.ts# Denodeno task start

Выбор между Node, Bun и Deno#

Универсального ответа нет, и то, что ниже, - суждение, а не измерение.

Выбирайте Node.js, если у вас уже есть приложение с кучей зависимостей из npm, если вы полагаетесь на нативные аддоны, если команда его уже знает или если нужно наименее неожиданное поведение. Почти на любую проблему с deploy, с которой вы столкнётесь, для Node уже есть написанный ответ. Цена - этап компиляции TypeScript, и он невелик.

Выбирайте Bun, если начинаете новый сервис на TypeScript, хотите один инструмент для установки, запуска и тестов, а ваши зависимости - обычный JavaScript или заведомо работают на Bun. Это самый близкий к прямой замене вариант для проекта на Node, потому что он читает package.json и node_modules, так что проба стоит вечера, а не переписывания. Прежде чем фиксировать выбор, прогоните под ним тесты.

Выбирайте Deno, если вам нравится модель разрешений, если вы хотите встроенные deno check, deno fmt и deno lint или если пишете на веб-стандартных API и импортируете из JSR. Он хорошо подходит для небольшого API с малым числом зависимостей. Хуже он подходит для большой кодовой базы на Express, которую никогда не писали с оглядкой на разрешения.

Если вы не уверены, стройте приложение на веб-стандартных Request и Response с небольшим фреймворком вроде Hono, держите зависимости на чистом JavaScript, и тогда вы сможете переходить между всеми тремя, изменив лишь входной файл. Такая переносимость стоит больше любой заявленной разницы в скорости.

Устранение неполадок#

Сервер запускается, но ничего не отвечает. Адрес привязки или порт. Проверьте, что хост - 0.0.0.0, а порт - тот, который выделен тарифом. Откройте вкладку Network, сравните и посмотрите на строку лога, которую печатает приложение.

`error: Cannot find module` на Bun после deploy. Установка не выполнилась или выполнилась с неверными флагами. Выполните bun install --frozen-lockfile в команде запуска и прочитайте вывод в консоли. Если пакету нужен шаг postinstall, добавьте его в trustedDependencies.

`error: Requires net access` или `Requires env access` на Deno. Не хватает разрешения. Ошибка называет разрешение и часто точный хост или переменную. Добавьте узкий флаг, а не -A.

`error: Relative import path "x" not prefixed with / or ./ or ../`. Deno нужен либо относительный путь с расширением, либо сопоставление в imports в deno.json. Добавьте сопоставление.

Несовпадение lockfile при установке. bun install --frozen-lockfile и --frozen у Deno завершаются ошибкой, когда манифест изменился без lockfile. Пересоздайте его локально, закоммитьте, выполните deploy. Не убирайте флаг на сервере.

Пакет работает локально, но не на сервере. Платформенно-зависимые необязательные зависимости, двоичные файлы или отсутствующий postinstall. Создавайте lockfile на машине с Linux или в CI и смотрите заметки пакета для вашего runtime.

График памяти растёт, а затем сервер перезапускается. Утечка, неограниченный кэш или большая нагрузка, удерживаемая в памяти. Прочитайте раздел о памяти выше, ограничьте кэши и проверьте в консоли шаблон перезапусков. О том, на что смотреть, рассказано в статье как читать консоль.

WebSocket подключается локально, но обрывается через прокси. Тайм-ауты простоя и обработка upgrade на пути через прокси. См. WebSocket за обратным прокси.

FAQ#

Готов ли Bun к production?

Его используют в production многие команды, и при этом он моложе Node, а запись о его совместимости менее полна. Готов ли он для вашего приложения, зависит от ваших зависимостей. Прогоните под ним набор тестов, проведите нагрузочное тестирование важных для вас маршрутов и сохраните возможность вернуться на Node на один-два релиза.

Можно ли запустить существующее приложение на Express на Bun или Deno?

Обычно да, через слои совместимости с Node, но относитесь к этому как к миграции с проходом тестов, а не как к переключению флага. На Deno вы добавляете спецификаторы npm: или package.json, а также разрешения. Следите за нативными модулями и всем, что зависит от внутренностей Node.

Нужен ли этап сборки для TypeScript?

На Bun и Deno нет, они запускают файлы .ts напрямую. Проверка типов всё равно нужна где-то ещё, потому что ни одна команда запуска не отвергает ошибку типов. Добавьте tsc --noEmit или deno check в CI.

Что расходует меньше памяти, Bun или Deno?

Это зависит от приложения, версий и нагрузки, и любая отдельная цифра была бы догадкой. Измерьте собственное приложение на тарифе, который собираетесь использовать, и сравните график с лимитом.

Как называется lockfile у Bun?

bun.lock, текстовый файл, в современных версиях. Старые версии писали двоичный bun.lockb. Фиксируйте lockfile в репозитории и устанавливайте на сервере командой bun install --frozen-lockfile.

Работают ли флаги разрешений Deno на хостинговом тарифе?

Да. Это флаги deno run, поэтому они входят в команду запуска или в задачу deno.json. Они ограничивают ваш процесс; контейнер хостинга - отдельный слой изоляции.