Zen Notes
Overview
Zen (published here as Zen Notes to disambiguate the generic upstream name) is an open-source, self-hosted notes application built as a single Go binary. Every note is stored as standard Markdown inside a local SQLite database. Zen adds full-text search (BM25 ranking), flexible tags instead of rigid folders, templates, pinned and archived notes, and image uploads, all in a fast, low-resource web app.
The SecureAnyCloud image delivers Zen fully built and reverse-proxied with nginx over HTTPS. The Zen server is a single self-contained binary bound to the loopback interface, and nginx terminates TLS in front of it. The database and uploads live on a dedicated Azure data disk mounted at /var/lib/zen, separate from the OS disk and re-provisioned with every VM.
What you get:
- Zen 1.5.0 (built from source, AGPL-3.0) as a single Go binary at
/usr/local/bin/zen, bound to loopback127.0.0.1:8080 - nginx terminating TLS on
:443in front of the Zen server, with:80redirecting to HTTPS - A dedicated Azure data disk at
/var/lib/zenholding the SQLite database (zen.db) and uploaded images, separate from the OS disk and independently resizable - A per-VM administrator account generated on first boot, with no default or shared credential in the image
zen.serviceandnginx.serviceas systemd units, enabled and active- 24/7 SecureAnyCloud support
Secure by default: a per-VM administrator credential
Zen is a single-user application. This image ships with no default or shared login, and the SQLite database ships empty of user accounts.
On first boot, a one-shot service generates a unique administrator password for your VM, hashes it, seeds exactly one admin account (admin@zen.local) and writes the credential to a root-only file. The Zen server is deliberately held back until that account exists, so there is never a window in which a fresh VM is reachable without an administrator.
You retrieve the password over SSH and change it after your first login.
Prerequisites
Before you deploy, make sure you have:
- An active Azure subscription
- An SSH key pair
- A VNet and subnet in the target region
Recommended VM size: Standard_B2s (2 vCPU / 4 GiB RAM) is a comfortable starting point. Scale up for large note or image volumes.
NSG inbound rules: allow 22/tcp from your management network, and 80/tcp and 443/tcp from wherever you browse Zen (:80 only redirects to :443).
Step 1 - Deploy from the Azure Marketplace
- Sign in to the Azure Portal, choose Create a resource, search the Marketplace for Zen Notes by SecureAnyCloud, and select Create.
- On Basics, pick your subscription, resource group, region and size.
- Under Administrator account, choose SSH public key and paste your key.
- Under Inbound port rules, allow SSH (22), HTTP (80) and HTTPS (443).
- Review the dedicated data disk on the Disks tab, then select Review + create and Create.
Step 2 - Deploy from the Azure CLI
# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn sac1647283583153:zen-notes:default:latest
az vm create \
--resource-group <your-rg> \
--name zen-notes \
--image sac1647283583153:zen-notes:default:latest \
--size Standard_B2s \
--admin-username azureuser \
--ssh-key-values ~/.ssh/id_ed25519.pub \
--vnet-name <your-vnet> --subnet <your-subnet> \
--public-ip-sku Standard
az vm open-port --resource-group <your-rg> --name zen-notes --port 80 --priority 1010
az vm open-port --resource-group <your-rg> --name zen-notes --port 443 --priority 1020Step 3 - Connect to your VM
Connect to the VM over SSH:
ssh azureuser@<vm-public-ip>The message of the day shows your VM's Zen URL and administrator email.
Step 4 - Confirm the services are running
Zen runs as two systemd units, the Go server and nginx. Check that both are active:
systemctl is-active zen.service nginx.serviceactive activeThe Zen server listens only on the loopback interface, and nginx is the only service bound to public ports (:80 and :443):
sudo ss -tln | grep -E ':(80|443|8080) 'LISTEN 0 511 0.0.0.0:443 0.0.0.0:* LISTEN 0 511 0.0.0.0:80 0.0.0.0:* LISTEN 0 4096 127.0.0.1:8080 0.0.0.0:*The web UI is served over HTTPS, and plain HTTP redirects to it:
curl -sk -o /dev/null -w 'https:// -> %{http_code}\n' https://127.0.0.1/
curl -s -o /dev/null -w 'http:// -> %{http_code}\n' http://127.0.0.1/https:// -> 200 http:// -> 301Step 5 - Read the per-VM administrator credential
On first boot the VM writes a root-only file with your Zen URL, the administrator email and the unique password generated for this VM:
sudo grep -E '^ZEN_URL=|^ZEN_ADMIN_EMAIL=' /root/zen-notes-credentials.txtZEN_URL=https://<vm-public-ip>/ ZEN_ADMIN_EMAIL=admin@zen.localThe ZEN_ADMIN_PASSWORD line in that file holds your unique password. Exactly one account is seeded, and only the per-VM password authenticates. A wrong or blank password is rejected:
EMAIL=$(sudo grep '^ZEN_ADMIN_EMAIL=' /root/zen-notes-credentials.txt | cut -d= -f2-)
PASS=$(sudo grep '^ZEN_ADMIN_PASSWORD=' /root/zen-notes-credentials.txt | cut -d= -f2-)
curl -sk -o /dev/null -w 'per-VM password -> HTTP %{http_code}\n' -X POST https://127.0.0.1/api/users/login \
-H 'Content-Type: application/json' --data "{\"email\":\"$EMAIL\",\"password\":\"$PASS\"}"
curl -sk -o /dev/null -w 'wrong password -> HTTP %{http_code}\n' -X POST https://127.0.0.1/api/users/login \
-H 'Content-Type: application/json' --data "{\"email\":\"$EMAIL\",\"password\":\"wrong-nope\"}"per-VM password -> HTTP 200 wrong password -> HTTP 400Step 6 - First login
Open Zen in your browser. Accept the self-signed certificate warning, or install a trusted certificate first, as described in Enabling a trusted TLS certificate:
https://<vm-public-ip>/Enter the administrator email (admin@zen.local) and the ZEN_ADMIN_PASSWORD from Step 5, then click Login. Immediately after your first sign-in, open Settings from the sidebar and change the password.
Capturing notes
Click New to start a note. Zen uses Markdown, so headings, bold, lists, tables, task checklists and fenced code all render as you write. Add #tags inline to organise notes. Tags let you filter without folders. Your notes appear in a timeline on the left, with full-text search across everything.
Markdown tables render inline, so structured notes stay readable:
Task lists, fenced code blocks with syntax highlighting, tags and more all render from standard Markdown:
Step 7 - The single Go binary
Zen is a single self-contained binary that embeds the web UI and database migrations. The image uses a tiny zen-passhash helper only at first boot, to seed the administrator password:
ls -la /usr/local/bin/zen /usr/local/bin/zen-passhash file /usr/local/bin/zen | cut -d, -f1-3-rwxr-xr-x 1 root root 16246024 /usr/local/bin/zen -rwxr-xr-x 1 root root 2557168 /usr/local/bin/zen-passhash /usr/local/bin/zen: ELF 64-bit LSB executable, x86-64The data disk
Zen's SQLite database (zen.db) and uploaded images live on a dedicated Azure data disk mounted at /var/lib/zen, independently resizable and kept separate from the OS disk:
df -h /var/lib/zenFilesystem Size Used Avail Use% Mounted on /dev/sda 30G 240K 28G 1% /var/lib/zenThe device letter can vary between boots. The mount is keyed by filesystem UUID in /etc/fstab, so /var/lib/zen is always the dedicated data disk.
To grow the disk, resize the data disk in the Azure portal, then extend the filesystem with sudo resize2fs against the device shown by lsblk. Snapshot the data disk in Azure to back up your notes.
Security updates
The image is patched at build time (including Ubuntu phased updates), and unattended-upgrades remains enabled, so later security patches keep flowing to your VM:
echo "held=[$(apt-mark showhold)]"; systemctl is-enabled unattended-upgrades.serviceheld=[] enabledEnabling a trusted TLS certificate
The image ships a per-VM self-signed certificate so HTTPS works out of the box; browsers warn until you install a trusted certificate. For production, point a DNS A record at the VM's public IP and make sure 443/tcp is open in the NSG. Then install certbot and let it manage the nginx certificate. Replace the example domain and email address with your own:
sudo apt-get update
sudo apt-get install -y certbot python3-certbot-nginx
sudo certbot --nginx -d your-domain.example.com -m you@example.com --agree-toscertbot configures the HTTPS server block and sets up automatic renewal. When it completes, Zen is available at https://your-domain.example.com/ with a trusted certificate.
Upgrading Zen
Zen is installed as a single Go binary at /usr/local/bin/zen. To upgrade within the 1.x line, build the new tag from source (make build in the upstream repository), replace the binary and restart zen.service.
Always snapshot the data disk first, because the schema migrates on start. SecureAnyCloud support can assist with planning and performing upgrades.
Support
This image is backed by 24/7 SecureAnyCloud support covering deployment, upgrades, integrations, TLS termination and database administration. Contact us by email or chat.
Zen is a trademark of its respective owner. All product and company names are trademarks or registered trademarks of their respective holders. Use of them does not imply any affiliation with or endorsement by them.
Deploy on Azure
Launch Zen Notes 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