Deno is a JavaScript and TypeScript runtime from the creator of Node.js. It runs TypeScript natively, includes a formatter, linter, test runner and bundler, uses web-standard APIs (fetch, Request, Response), and is secure by default: scripts can’t access the network, files or environment unless you allow it. Deno 2 added strong npm and Node.js compatibility.

Install

curl -fsSL https://deno.land/install.sh | sh

It installs to ~/.deno/bin and offers to update your shell profile. Then:

source ~/.bashrc
deno --version

For system-wide use, copy the single binary: sudo cp ~/.deno/bin/deno /usr/local/bin/. Upgrade with deno upgrade.

A minimal HTTP server

// main.ts
Deno.serve({ hostname: "127.0.0.1", port: 8000 }, (req) => {
  const url = new URL(req.url);
  if (url.pathname === "/health") return new Response("ok");
  return new Response("Hello from Deno");
});
deno run --allow-net=127.0.0.1:8000 main.ts

Permissions

Without flags, Deno denies access and prompts (or fails in non-interactive mode). Grant only what’s needed:

Flag Allows
--allow-net=host:port Network access (optionally limited)
--allow-read=/srv/app/data Reading files under a path
--allow-write=/srv/app/data Writing files
--allow-env=DATABASE_URL,PORT Reading specific environment variables
--allow-run=git Running subprocesses
-A / --allow-all Everything (avoid in production)

Narrow permissions limit the damage if a dependency is compromised — a real advantage over runtimes where every package can do anything.

Using npm packages

import express from "npm:express@4";

Or add a deno.json with an import map and run deno install to manage dependencies with a lockfile (deno.lock). Most npm packages work; Node built-ins are available as node:fs, node:path etc.

Run it with systemd

sudo useradd --system --create-home --home-dir /srv/denoapp --shell /usr/sbin/nologin denoapp

/etc/systemd/system/denoapp.service:

[Unit]
Description=Deno app
After=network.target

[Service]
User=denoapp
WorkingDirectory=/srv/denoapp/app
Environment=DENO_DIR=/srv/denoapp/.cache/deno
ExecStart=/usr/local/bin/deno run --allow-net=127.0.0.1:8000 --allow-env=PORT,DATABASE_URL --allow-read=. main.ts
Restart=always
RestartSec=3

[Install]
WantedBy=multi-user.target

DENO_DIR points the module cache to a writable location for the service user. Cache dependencies before starting, so the first request doesn’t wait on downloads:

sudo -u denoapp env DENO_DIR=/srv/denoapp/.cache/deno deno cache main.ts
sudo systemctl daemon-reload && sudo systemctl enable --now denoapp

Nginx and HTTPS

Proxy to 127.0.0.1:8000 like any other app — see the Nginx reverse proxy guide — and add a certificate with certbot --nginx. Or use Caddy for automatic HTTPS.

Compile to a standalone binary

deno compile --allow-net=127.0.0.1:8000 --output myapp main.ts
./myapp

The permissions are baked into the binary. Deploy one file without installing Deno on the server.

Built-in tools

deno fmt          # format
deno lint         # lint
deno test         # tests
deno check main.ts   # type-check
deno task start   # tasks defined in deno.json

Docker

The official denoland/deno images are available; run as the non-root deno user and pass the same permission flags in CMD.

Deno vs Node vs Bun

  • Deno — security model, TypeScript and tooling built in, web-standard APIs.
  • Node.js — largest ecosystem, most battle-tested.
  • Bun — speed-focused, Node-compatible.

All three run well on a small Linux VPS.

  • Deno
  • JavaScript
  • TypeScript