Tutorial

Bun and Deno on a Container Host: The Dockerfiles, the Permissions, and What Is Not Detected for You

SnapDeploy Team 2026-11-25 Updated 2026-11-25 7 min read
bundenotypescriptdockerfree-tier

Bun and Deno both run TypeScript without a build step, start fast and use less memory than Node for the same service, which makes them a natural fit for a 512 MB container. What they do not get on most container hosts is automatic detection: a repository with bun.lock or deno.json looks like a Node project to a generator that only knows package.json. On SnapDeploy the generated Dockerfile is Node-only, so for either runtime you bring a Dockerfile. Here is one for each, with the permissions, ports and lock-file details that decide whether the first deploy works.

Tested with Bun 1.4 and Deno 2.9, the current stable releases in September 2026. Both publish multi-architecture images, so they run unchanged on a host that builds ARM64 images.

Bun

// src/index.ts
const port = Number(Bun.env.PORT ?? 3000);
Bun.serve({
  port,
  hostname: "0.0.0.0",
  fetch(req) {
    const url = new URL(req.url);
    if (url.pathname === "/health") return new Response("ok");
    if (url.pathname === "/api/time") return Response.json({ now: new Date().toISOString() });
    return new Response("not found", { status: 404 });
  },
});
console.log(`listening on :${port}`);
FROM oven/bun:1-alpine AS deps
WORKDIR /app
COPY package.json bun.lock ./
RUN bun install --frozen-lockfile --production

FROM oven/bun:1-alpine
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ENV NODE_ENV=production
USER bun
EXPOSE 3000
CMD ["bun", "run", "src/index.ts"]
  • oven/bun:1-alpine tracks the current 1.x release; pin oven/bun:1.4-alpine for reproducible builds. The Alpine image is about 100 MB; the Debian-based default is larger.
  • --frozen-lockfile fails the build if bun.lock is out of date, which is what you want on a hosted build. Commit the lock file.
  • Bun runs TypeScript directly; there is no build step unless you want one. For a smaller image, bun build --compile produces a single executable you can copy onto a bare Alpine stage.
  • The bun user exists in the official image; run as it.

Memory: the service above idles around 30 to 40 MB, roughly half of the equivalent Node process. Frameworks on top (Hono, Elysia, or Express, which Bun runs through its Node compatibility) add a little. Start-up is well under a second.

Deno

// main.ts
const port = Number(Deno.env.get("PORT") ?? "8000");
Deno.serve({ port, hostname: "0.0.0.0" }, (req) => {
  const { pathname } = new URL(req.url);
  if (pathname === "/health") return new Response("ok");
  if (pathname === "/api/time") return Response.json({ now: new Date().toISOString() });
  return new Response("not found", { status: 404 });
});
FROM denoland/deno:alpine
WORKDIR /app
COPY deno.json deno.lock ./
COPY . .
RUN deno cache main.ts
USER deno
EXPOSE 8000
CMD ["deno", "run", "--allow-net", "--allow-env", "main.ts"]
  • deno cache at build time downloads and compiles dependencies into the image so the container does not fetch anything at start. With a deno.lock committed, the build fails if the lock does not match, which again is what you want.
  • Permissions are explicit: --allow-net to listen and make requests, --allow-env to read PORT and secrets. Add --allow-read only if the app reads files. Granting -A (everything) works but throws away the one thing Deno does that Node does not.
  • denoland/deno:alpine is the small image; pin denoland/deno:2.9.6 (Debian-based) or the matching Alpine tag for reproducible builds.
  • For a single-binary image, deno compile produces an executable you can copy onto a bare stage, at the cost of a longer build.

Memory is similar to Bun, around 30 to 50 MB idle for a small service; npm packages imported through npm: specifiers work and are cached the same way.

What the platform does and does not do for these

  • Does: use your Dockerfile as written, read the port from EXPOSE, inject environment variables and add-on connection strings, build for ARM64 (both official images support it), show build and runtime logs.
  • Does not: detect Bun or Deno from the repository. A package.json next to bun.lock will be treated as a Node project and started with npm start on a Node image, which fails for anything that relies on Bun APIs. The Dockerfile settles it.
  • Does not: scan Bun or Deno code for environment variables. Add a .env.example to the repository and the variables it lists are offered to you before the first deploy.

Deploying either

  1. Commit the Dockerfile, the lock file and a .dockerignore (node_modules, .git, .env).
  2. Push. Containers → Deploy from GitHub → pick the repository. Port 3000 for the Bun example, 8000 for Deno.
  3. Add environment variables; attach a database add-on if needed (DATABASE_URL is injected).
  4. Deploy, then open /health and /api/time on the container URL.

Test the image before you push

docker build -t svc .
docker run --rm -p 3000:3000 -e PORT=3000 svc
curl -s http://localhost:3000/health

The two failures you will meet are both visible here: a lock file that does not match (build fails at install) and a Deno permission you forgot (the process exits on the first denied call, with a line telling you which flag to add).

A database

Both runtimes talk to Postgres with the same libraries Node uses (postgres, pg, or Drizzle on top), and Deno has its own @db/postgres. Read DATABASE_URL from the environment, cap the pool at a few connections, and pick the database the same way every guide here does: a PostgreSQL Mini ($29/month), a $1 DB Sprint Pack for 12 hours, or a free external plan such as Neon.

Where free stops

  • Sleep after 15 minutes without traffic, about a minute to wake; both runtimes start so fast that the wake is almost entirely the container itself.
  • 100 container-hours a month across four containers; a small service visited occasionally uses a fraction.
  • Custom domains and no sleeping: Always-On, $12/month per container.

Picking one

If the project already has package.json and npm dependencies, Bun is the drop-in: same layout, faster installs, and most Node libraries work. If you are starting fresh and want permissions, first-class TypeScript and a standard library without a package manager, Deno is the cleaner design. On a container both are the same size and speed for a small service; the choice is about the ecosystem you want, not the host.

Free tier: 4 containers, 512 MB and 0.25 vCPU each, 100 container-hours a month, 10 deploys a day, no card; details on the free tier docs page. Bun and Deno versions as published on bun.com and deno.com in September 2026.

Ready to Deploy?

Deploy free. 10 deploys a day, 100 hours a month, no credit card.

Run this yourself: Get a dedicated NVIDIA T4 (16 GB) for a flat $499/mo or A10G (24 GB) for $999/mo — never shared, never spot, no hourly metering. See dedicated GPU hosting →

One-click Ollama, vLLM, ComfyUI, Whisper, PyTorch & more — or deploy your own GitHub repo or Docker image. Compare plans.

FIRST OF ITS KIND

Sprint Pack — ₹99 for 24 hours

The first managed container PaaS to offer a one-time 24-hour Always-On pass under $5. Unlimited deploys + Always-On for one Small (512 MB) container — including WebSockets & real-time apps. No subscription, no auto-renewal. Stack two for 48 hours.

Need a managed database too? DB Sprint Pack — ₹99 / 12h for Postgres, MySQL, MariaDB, Mongo, Redis, or RabbitMQ.

Tip: Need 24 hours of Always-On for a demo, weekend, or quick test — or WebSockets & real-time apps? Sprint Pack is ₹99 one-time — no subscription, no auto-renewal.

Need a managed Postgres / MySQL / Mongo / Redis instead? DB Sprint Pack — ₹99 / 12h.

Take SnapDeploy with you

Deploy, monitor and wake your containers from your iPhone.

Download on the App Store

Get DevOps Tips & Updates

Container deployment guides, platform updates, and DevOps best practices. No spam.

Unsubscribe anytime. We respect your privacy.

More Articles