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.