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, onPATHfor every user. deno-demo.servicesystemd unit that starts on boot. It runs as the unprivilegeddeno:denosystem user with only--allow-net=127.0.0.1:8000granted.deno-firstboot.servicesystemd 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
/healthzendpoint (nginx-native, HTTP 200) for load balancer and probe checks. - A pre-deployed demo
Deno.serve()app at/var/lib/deno-apps/demo/main.tsthat returns a live server timestamp and a request counter on every call. /var/lib/deno-appson 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, useStandard_D4s_v5(4 vCPU, 16 GB RAM) or larger.
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)
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 StandardConnect 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.
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.
Check the Health Endpoint
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1/healthzExpected 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.
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>/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.serviceExpected 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.targetThe 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.
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.targetsudo systemctl daemon-reload
sudo systemctl enable --now myapp.serviceRun 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.
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.notesExpected 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.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
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-pagerDeno'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.
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
:80and:443are 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:denosystem user.
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