Deployment guide

Deno 2.9

Overview

This guide covers the deployment and configuration of Deno 2.9 on Ubuntu 24.04 on Azure, using SecureAnyCloud Azure Marketplace images. Deno is a modern, secure-by-default JavaScript/TypeScript runtime. It has built-in TypeScript support with no separate compile step, and it runs as a single binary with no node_modules.

Its defining feature is a permissions sandbox: a Deno program has no ambient access to the filesystem, network, or environment unless you explicitly grant it.

The image installs the official pinned Deno v2.9.2 release binary from the denoland/deno GitHub release, verified against a sha256 checksum, to /usr/local/bin/deno (already on the default PATH for every user). Deno has no admin console or login, so there is nothing to authenticate to. Instead, the image ships a small demo HTTP API, pre-deployed and running under systemd. It shows that the runtime executes code on every request (a live server timestamp and a request counter) and does not just serve static files.

The demo's systemd unit grants only --allow-net=127.0.0.1:8000, with no filesystem, environment, or subprocess access. This demonstrates Deno's sandboxing model correctly.

What you get:

  • Deno v2.9.2 (official release binary, sha256-pinned) installed to /usr/local/bin/deno, on PATH for every user.
  • deno-demo.service systemd unit that starts on boot. It runs as the unprivileged deno:deno system user with only --allow-net=127.0.0.1:8000 granted.
  • deno-firstboot.service systemd oneshot that starts the demo app and nginx, and confirms the demo answers before completing.
  • The demo API bound to loopback only (127.0.0.1:8000), with nginx in front of it on :80.
  • An unauthenticated /healthz endpoint (nginx-native, HTTP 200) for load balancer and probe checks.
  • A pre-deployed demo Deno.serve() app at /var/lib/deno-apps/demo/main.ts that returns a live server timestamp and a request counter on every call.
  • /var/lib/deno-apps on a dedicated 20 GiB Azure data disk, so deployed app code and any app-level data and logs survive independently of the OS disk.
  • Ubuntu 24.04 LTS base with the latest security patches applied at build time.
  • Azure Linux Agent for cloud integration and SSH key injection.
  • 24/7 SecureAnyCloud support with a guaranteed 24 hour response SLA.

Prerequisites

  • An active Azure subscription, an SSH public key, and a VNet and subnet in your target region.
  • A subscription to the Deno 2.9 listing on Azure Marketplace.
  • Recommended virtual machine size: Standard_B2s (2 vCPU, 4 GB RAM) for development and light API workloads. For CPU-bound or high-concurrency workloads, use Standard_D4s_v5 (4 vCPU, 16 GB RAM) or larger.
1

Deploy from the Azure Portal

Search for Deno in Azure Marketplace, select the SecureAnyCloud publisher, and click Create.

Add these NSG rules, with your client networks as the source:

  • TCP 22 (admin)
  • TCP 80 (HTTP)
  • TCP 443 (reserved for TLS)
2

Deploy from the Azure CLI

RG="deno-prod"; LOCATION="eastus"; VM_NAME="deno-01" IMAGE_URN="sac1647283583153:deno-ubuntu-24-04:default:latest" SSH_KEY="$(cat ~/.ssh/id_rsa.pub)" az group create --name "$RG" --location "$LOCATION" az network vnet create -g "$RG" --name deno-vnet --address-prefix 10.100.0.0/16 --subnet-name deno-subnet --subnet-prefix 10.100.1.0/24 az network nsg create -g "$RG" --name deno-nsg az network nsg rule create -g "$RG" --nsg-name deno-nsg --name allow-ssh --priority 100 \
  --source-address-prefixes "<your-mgmt-cidr>" --destination-port-ranges 22 --access Allow --protocol Tcp az network nsg rule create -g "$RG" --nsg-name deno-nsg --name allow-http --priority 110 \
  --destination-port-ranges 80 --access Allow --protocol Tcp az network nsg rule create -g "$RG" --nsg-name deno-nsg --name allow-https --priority 120 \
  --destination-port-ranges 443 --access Allow --protocol Tcp
# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn sac1647283583153:deno-ubuntu-24-04:default:latest
az vm create -g "$RG" --name "$VM_NAME" --image "$IMAGE_URN" \
  --size Standard_B2s --storage-sku StandardSSD_LRS \
  --admin-username azureuser --ssh-key-values "$SSH_KEY" \
  --vnet-name deno-vnet --subnet deno-subnet --nsg deno-nsg --public-ip-sku Standard
3

Connect via SSH

ssh azureuser@<vm-ip>

deno-demo.service, nginx.service and deno-firstboot.service all start automatically on first boot. There is no admin account to set up, because Deno has no login.

4

Verify the Deno Demo and nginx Services

sudo systemctl status deno-demo.service --no-pager sudo systemctl status nginx.service --no-pager sudo test -f /var/lib/sac/data/deno-firstboot.done && echo FIRSTBOOT_DONE
sudo ss -tln | grep -E ':(80|8000) '

Expected output:

● deno-demo.service - Deno demo HTTP API (sac) Active: active (running) Main PID: 2107 (deno) CGroup: /system.slice/deno-demo.service └─2107 /usr/local/bin/deno run --allow-net=127.0.0.1:8000 /var/lib/deno-apps/demo/main.ts ...Listening on http://127.0.0.1:8000/ ● nginx.service - A high performance web server and a reverse proxy server Active: active (running) FIRSTBOOT_DONE LISTEN 0 511 127.0.0.1:8000 0.0.0.0:* LISTEN 0 511 0.0.0.0:80 0.0.0.0:* LISTEN 0 511 [::]:80 [::]:*

The demo app is bound to 127.0.0.1:8000 only. Deno is never directly reachable from the network, and nginx on :80 is the only public entry point.

5

Check the Health Endpoint

curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1/healthz

Expected output: 200. nginx serves this unauthenticated endpoint directly (it is not proxied to Deno), so it always answers, even under load. Use it for a load balancer or uptime probe.

6

Call the Pre-Deployed Demo API

Deno has no admin UI or login. Instead, the image has a small demo Deno.serve() API pre-deployed at the server root. Request it twice in a row:

curl -s http://127.0.0.1/
curl -s http://127.0.0.1/

Expected output. serverTime and requestCount both change on every call:

{ "message": "Deno demo API running on sac", "denoVersion": "2.9.2", "serverTime": "2026-07-09T18:12:32.026Z", "requestCount": 1 } { "message": "Deno demo API running on sac", "denoVersion": "2.9.2", "serverTime": "2026-07-09T18:12:32.034Z", "requestCount": 2 }

This shows that the runtime is executing code on every request. An in-process counter and a live server timestamp cannot come from a cached or static file.

From your workstation, open the demo in a browser. The NSG must allow TCP 80:

open http://<vm-ip>/
7

Confirm the Deno Version and Least-Privilege Permissions

deno --version find /var/lib/deno-apps -maxdepth 2 -not -path '*/.cache*' cat /etc/systemd/system/deno-demo.service

Expected output:

deno 2.9.2 (stable, release, x86_64-unknown-linux-gnu) v8 14.9.207.2-rusty typescript 6.0.3 --- /var/lib/deno-apps /var/lib/deno-apps/demo /var/lib/deno-apps/demo/main.ts /var/lib/deno-apps/lost+found --- [Unit] Description=Deno demo HTTP API (sac) After=network.target [Service] Type=simple User=deno Group=deno WorkingDirectory=/var/lib/deno-apps/demo ExecStart=/usr/local/bin/deno run --allow-net=127.0.0.1:8000 /var/lib/deno-apps/demo/main.ts Restart=on-failure RestartSec=10 NoNewPrivileges=yes [Install] WantedBy=multi-user.target

The ExecStart line grants only --allow-net=127.0.0.1:8000, scoped to the exact loopback address and port the demo binds. There is no --allow-all, no unscoped --allow-net, and no --allow-read/--allow-write/--allow-env/--allow-run.

This is Deno's permissions sandbox working as designed. The runtime refuses filesystem, environment, or subprocess access to this program because it was never granted.

8

Deploy Your Own TypeScript/JavaScript App

Copy your own app into a new directory under /var/lib/deno-apps/, then add a systemd unit that grants only the permissions it needs:

# Copy your app to the VM scp -r ./myapp azureuser@<vm-ip>:/tmp/myapp ssh azureuser@<vm-ip> 'sudo mv /tmp/myapp /var/lib/deno-apps/myapp && sudo chown -R deno:deno /var/lib/deno-apps/myapp'

Then create /etc/systemd/system/myapp.service on the VM, following the same least-privilege pattern as the demo unit. Grant only what your app needs. For example, add --allow-read=/var/lib/deno-apps/myapp/data if it reads local files, or --allow-env=API_KEY for a single environment variable. Never use --allow-all:

[Unit] Description=My Deno app After=network.target [Service] Type=simple User=deno Group=deno WorkingDirectory=/var/lib/deno-apps/myapp ExecStart=/usr/local/bin/deno run --allow-net=127.0.0.1:8001 /var/lib/deno-apps/myapp/main.ts Restart=on-failure [Install] WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now myapp.service

Run these commands on your own VM against your own unit file. myapp.service does not exist on a fresh image.

Then add a second nginx location block (or a new port) to route to your app. The demo app on :8000 can keep running alongside your own app on a different loopback port.

9

Read the Endpoint Notes

At first boot, deno-firstboot.service writes a non-secret notes file that documents the deployed URLs. Deno has no credentials to rotate.

sudo cat /var/lib/sac/data/deno-endpoints.notes

Expected output:

# Deno — Endpoint Notes
# Generated on first boot by deno-firstboot.service
#
# Deno itself has no admin UI or login — there is nothing to authenticate to.
# DENO_DEMO_URL=http://<vm-ip>/ DENO_HEALTHZ_URL=http://<vm-ip>/healthz DENO_BINARY=/usr/local/bin/deno DENO_APPS_DIR=/var/lib/deno-apps DEPLOY_YOUR_OWN_APP=Drop a .ts/.js app into /var/lib/deno-apps/<name>/, add a systemd unit granting only the permissions it needs (e.g. --allow-net), then enable + start it.
10

Server Components

  • Deno binary: /usr/local/bin/deno
  • App data disk: /var/lib/deno-apps
  • Demo app: /var/lib/deno-apps/demo/main.ts
  • Logs: systemd journal (journalctl -u deno-demo.service)
  • Systemd unit: /etc/systemd/system/deno-demo.service
  • Firstboot script: /usr/local/sbin/deno-firstboot.sh
  • Firstboot service: /etc/systemd/system/deno-firstboot.service
  • Endpoint notes: /var/lib/sac/data/deno-endpoints.notes (mode 0644)
  • Firstboot sentinel: /var/lib/sac/data/deno-firstboot.done
  • nginx vhost: /etc/nginx/sites-available/sac-deno
11

Managing the Deno Demo Service

sudo systemctl status deno-demo.service --no-pager sudo systemctl restart deno-demo.service
sudo journalctl -u deno-demo.service -n 50 --no-pager

Deno's stdout and stderr go to the systemd journal (journalctl -u deno-demo.service). Run sudo journalctl -u deno-demo.service -f to follow the log live.

12

Security Recommendations

  • Never expose port 8000 directly. It is bound to loopback only by design. Always go through nginx (:80) or add a TLS-terminating reverse proxy on :443.
  • Add TLS by fronting nginx with Let's Encrypt/Certbot.
  • Restrict the NSG so :80 and :443 are reachable only from your client networks if this is an internal service.
  • Always grant the minimum permissions your app needs. Start from --allow-net=<host>:<port> and add only what is required (--allow-read=<path>, --allow-env=<VAR>). Never use --allow-all.
  • Remove or replace the demo app (/var/lib/deno-apps/demo/) once you have deployed your own application.
  • Patch the OS monthly with apt-get update && apt-get upgrade && reboot.
  • Run Deno apps as a non-privileged user. This is already done with the deno:deno system user.
13

Support and Licensing

Deno is licensed under the MIT License. There is no per-CPU or per-deployment fee.

SecureAnyCloud provides commercial support for this image separately from the upstream project.

  • Email: sales@secureanycloud.com
  • Website: secureanycloud.com
  • Support hours: 24/7 with a guaranteed 24 hour response SLA

Deploy on Azure

Launch Deno 2.9 on Ubuntu 24.04 with 24/7 support from SecureAnyCloud.

View on Marketplace

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on Azure

Launch Deno 2.9 on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.

View this image

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on Azure