Deployment guide

Enclosed

Overview

Enclosed is a minimalist, open-source tool for sending private notes and secrets. You write a note or attach a file, and everything is encrypted in your browser with AES-256 before it leaves the page, so the server that stores it only ever holds unreadable ciphertext. Enclosed gives you a single link to share. The key that unlocks the note lives in the link fragment (the part after #), which browsers keep local and never send to a server.

The recipient opens the link and reads the note in their own browser, with no account and nothing to install. Before you send, you can lock a note behind an extra password, limit it to a single view so it self-destructs once read, and set it to expire after an hour, a day, a week or a month.

This SecureAnyCloud image runs the free, open-source, Apache-2.0 licensed Enclosed application the officially supported way, as the upstream Docker image pinned by digest, behind an nginx reverse proxy on port 80. Upstream defaults its JWT signing secret to a published value and ships with no login. This image ships with no known secret. A per-instance login and a per-instance JWT signing secret are generated for each VM on first boot, before the app is reachable. Authentication is required to create notes by default, so a fresh instance cannot be used as an open relay by a passer-by.

Enclosed is a trademark of its respective owner. This image is produced by SecureAnyCloud and is not affiliated with, endorsed by or sponsored by the Enclosed project. It ships the free, open-source, self-hosted software unmodified.

What you get:

  • Enclosed v1.16.0, the Apache-2.0 licensed Node application, run as the official upstream Docker image pinned by image digest
  • Docker Engine running the single Enclosed container, with its port published to 127.0.0.1:8787 (loopback only) and fronted by nginx on port 80
  • enclosed.service and enclosed-firstboot.service as systemd units, enabled and active on boot
  • A per-instance login and a per-instance JWT signing secret, generated for each VM on first boot and never baked into the image
  • Authentication required to create notes by default, so the instance is not a world-writable open relay
  • Filesystem storage for the opaque encrypted notes, created empty on first boot
  • No default login, no shipped secret and an empty store on first boot
  • An Ubuntu 24.04 LTS base with the latest security patches applied at build time
  • The Azure Linux Agent for cloud integration and SSH key injection
  • 24/7 SecureAnyCloud support with a 24-hour response SLA

Prerequisites

You need:

  • An active Azure subscription
  • An SSH public key
  • A VNet and subnet in the target region
  • A subscription to the Enclosed listing on Azure Marketplace

Recommended VM size: Standard_B2s (2 vCPU, 4 GB RAM) is a sensible starting point. Enclosed is a single lightweight Node service and runs comfortably there.

Configure the network security group (NSG) to allow inbound 22/tcp from your management network, 80/tcp while you set up, and 443/tcp once you have installed a TLS certificate (see Step 6).

HTTPS is required for real use. Enclosed encrypts and decrypts in the browser using the Web Crypto API, and browsers expose that API only on a secure origin. Over plain http:// to an IP address, the encryption cannot run, so the page will not create or read notes. Step 6 covers installing a certificate. Until then, reach the app only from http://localhost through an SSH tunnel, which browsers treat as secure (Step 4).

1

Deploy from the Azure Portal

Sign in to the Azure Portal, choose Create a resource, search the Marketplace for Enclosed by SecureAnyCloud, and select Create.

On the Basics tab, 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) and HTTP (80).

Select Review + create, then Create.

2

Deploy from the Azure CLI

RG="enclosed-prod"; LOCATION="eastus"; VM_NAME="enclosed-01" IMAGE_URN="sac1647283583153:enclosed-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 enclosed-vnet --address-prefix 10.100.0.0/16 --subnet-name enclosed-subnet --subnet-prefix 10.100.1.0/24 az network nsg create -g "$RG" --name enclosed-nsg az network nsg rule create -g "$RG" --nsg-name enclosed-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 enclosed-nsg --name allow-http --priority 110 \
  --destination-port-ranges 80 --access Allow --protocol Tcp
# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn sac1647283583153:enclosed-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 enclosed-vnet --subnet enclosed-subnet --nsg enclosed-nsg --public-ip-sku Standard

Replace <sub-id> and <version> with your subscription ID and the image version shown on the listing. Replace <your-mgmt-cidr> with the network you administer from.

3

First boot and per-instance credentials

On the first boot of each VM, before the app is reachable, Enclosed generates its own per-instance login and JWT signing secret. Nothing is baked into the image. Wait a minute after first boot, then SSH in and read the credentials file, which is 0600 root:root:

ssh azureuser@<vm-ip>
sudo cat /root/enclosed-credentials.txt

The file contains the account you use to create notes, with a per-instance random password:

  • ENCLOSED_ADMIN_EMAIL (<ADMIN_EMAIL>) and ENCLOSED_ADMIN_PASSWORD (<ADMIN_PASSWORD>) are the login you use in the web UI to create notes.
  • ENCLOSED_URL (<ENCLOSED_URL>) is the resolved address of this instance.

Confirm that the services and the container are healthy:

sudo systemctl is-active docker nginx enclosed-firstboot enclosed
active active active active
sudo docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'
NAMES STATUS PORTS enclosed Up 7 minutes 127.0.0.1:8787->8787/tcp
4

Open the app over a secure origin

Enclosed needs a secure origin for its in-browser encryption. Until you install a TLS certificate (Step 6), reach the app over an SSH tunnel from your workstation, because browsers treat http://localhost as a secure origin:

ssh -L 8080:127.0.0.1:80 azureuser@<vm-ip>

Leave the tunnel running and open http://localhost:8080/ in your browser. The login screen appears, because creating notes requires authentication by default. Sign in with ENCLOSED_ADMIN_EMAIL and ENCLOSED_ADMIN_PASSWORD from Step 3.

Type your secret into the note. On the right, choose an expiry, optionally set a one-time note password, and optionally switch on Delete after reading so the note self-destructs the first time it is opened. Then select Create note.

Enclosed confirms that the note was created and gives you a single share link and a QR code. The decryption key is the part of the link after #. It is generated in your browser and is never sent to the server. Copy the link and send it to your recipient over any channel.

When the recipient opens the link, Enclosed reads the key from the link fragment and decrypts the note in their browser. If you chose delete after reading, the note is destroyed on the server the moment it is read.

5

The security model

This image is built so that the server never sees your data and no usable secret ships in the image.

  • Zero-knowledge by design. Encryption and decryption happen only in the browser. The server stores opaque ciphertext, and the decryption key lives in the link fragment, which is never transmitted.
  • The app is bound to loopback. The Enclosed container publishes its port to 127.0.0.1:8787 only. nginx on port 80 is the single public front door, and the app is not reachable directly on the VM's address.
  • Authentication is required to create notes. A passer-by cannot use your instance as an open relay, because only the per-instance login can create notes. Recipients still need no account to read a public note.
  • No known secret ships. The JWT signing secret and the admin password are generated for each VM on first boot, before the app starts, replacing upstream's default. The app refuses to start if the secret is missing or still the default.

Run this check to see that the app is on loopback only, the public web is on port 80, an unauthenticated create is rejected, and the secret is per VM:

# app on loopback only, public web on :80, unauthenticated create rejected, per-VM secret ss -tlnH | grep -E ':(80|8787) ' | awk '{print $4}' | sort -u curl -s -o /dev/null -w 'public web GET / : %{http_code}\n' http://127.0.0.1/ curl -s -o /dev/null -w 'unauth create : %{http_code} (401 = rejected)\n' -X POST http://127.0.0.1/api/notes -H 'Content-Type: application/json' -d '{"payload":"x","deleteAfterReading":false,"ttlInSeconds":3600,"encryptionAlgorithm":"aes-256-gcm","serializationFormat":"cbor-array","isPublic":true}'

A built-in round-trip script exercises the whole model end to end on the instance. It confirms that:

  • an unauthenticated create is rejected
  • the per-VM admin can log in and create a note, and the same ciphertext reads back
  • a delete-after-reading note is gone on the second read
  • a nonexistent note is refused

Run it with:

sudo /usr/local/sbin/enclosed-roundtrip.sh
OK round-trip: url=http://<vm-ip> admin=admin@enclosed.local created+readback note <id> (payload matched); burn note <id> refused on 2nd read (404); unauth create 401; ghost 404
6

Install a TLS certificate (required for browser use)

Enclosed depends on the browser's Web Crypto API, which browsers disable on insecure origins. Without HTTPS, your users cannot create or read notes except over a localhost tunnel. Point a DNS record at the VM and open port 443 in your network security group. Then issue a certificate with Let's Encrypt:

sudo apt-get update
sudo apt-get install -y certbot python3-certbot-nginx
sudo certbot --nginx -d enclosed.example.com --redirect --agree-tos -m you@example.com

Certbot obtains the certificate, rewrites the nginx site to listen on 443 with HTTP redirected to HTTPS, and installs a renewal timer. Your users can now open https://enclosed.example.com/ directly, and the in-browser encryption works. Replace enclosed.example.com and the email address with your own.

Troubleshooting

  • The create button does nothing, or no notes are created in the browser. You are on an insecure origin. Use the SSH tunnel to http://localhost:8080/ (Step 4) or install a certificate (Step 6).
  • Login fails. Re-read the exact password with sudo cat /root/enclosed-credentials.txt. It is a per-VM random value.
  • Check the app logs. Run sudo docker logs --tail 100 enclosed and sudo systemctl status enclosed.
  • Allow anyone to create notes (open instance). Set PUBLIC_IS_AUTHENTICATION_REQUIRED=false in /etc/enclosed/enclosed.env and run sudo systemctl restart enclosed. Do this only behind HTTPS, and be aware that it makes note creation public.

Support

Email sales@secureanycloud.com for help with certificates, network rules, upgrades or branding. Critical issues receive a one-hour average response time, with a 24-hour response SLA.

Find this image on the Azure Marketplace and browse the rest of the catalogue at secureanycloud.com/products.

Deploy on Azure

Launch Enclosed 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