Puter
Overview
Puter is an open source, self-hosted desktop that runs entirely in a web browser. It gives you a desktop environment with a wallpaper, desktop icons, a task bar and draggable windows, on top of a full file manager for storage that lives on your own server. You can create, rename, move, upload, download and share files with other accounts on the same instance. Each account gets subdomains for publishing a static site or hosting a private web app. The puter.js SDK lets you build applications against your instance's own file, key-value and hosting APIs.
This SecureAnyCloud image installs Puter 26.07.1, built from the pinned upstream release with Node.js 24. It runs as a systemd service behind an nginx reverse proxy on port 80, and keeps its SQLite database and object storage under /var/lib/puter.
What you get:
- Puter 26.07.1 (AGPL-3.0), built from the pinned upstream release tag
- The web desktop, the windowed file manager and per-account storage, published on port 80 via nginx
- A unique administrator account generated on first boot and written to a root-only credentials file
- Unique JWT and URL signature secrets generated per VM, so no two deployments can forge each other's sessions
- Self-registration closed in the shipped configuration, for both permanent and anonymous temporary accounts
- SQLite plus a local object store under
/var/lib/puter, with no external database, cache or container runtime to operate puter-set-domain, a one-command helper for moving the appliance onto your own domainputer-s3.service,puter.serviceandnginx.serviceas systemd units, enabled and active- 24/7 SecureAnyCloud support
What is not included. Read this before you deploy:
- The self-hosted build registers no local applications. Every application entry that Puter seeds (Camera, Recorder, Dev Center, Git, PDF, Player and Music Player) points at a
puter.comor third-party hosted URL. No text editor is registered at all, so double-clicking a text file opens nothing. The desktop, the file manager, storage, sharing, the site and app hosting subdomains, and the SDK are what this appliance itself provides. - The desktop needs outbound internet from the browser. Puter's compiled interface loads its
puter.jsSDK fromhttps://js.puter.com/v2/at runtime. That URL is compiled into the upstream bundle, and this image ships upstream unmodified. If a browser cannot reachjs.puter.com, the sign-in page does not render. This appliance is not air-gappable. Puter's own code notes that self-hosters who need this can routejs.puter.comat their own instance via DNS. - The account area shows an "Upgrade" button and a dollar-denominated allowance. This is the hosted billing surface of
puter.comappearing in the self-hosted build. Nothing is charged, and there is no payment path on your VM.
Prerequisites
- An active Azure subscription, an SSH key pair, and a VNet with a subnet in the target region
- NSG inbound rules that allow
22/tcpfrom your management network and80/tcpfor the web interface. Front the interface with TLS for public exposure (see Step 10 - Enabling HTTPS) - Do not open
4100/tcpor4566/tcp. nginx on port 80 is the intended entrance
Standard_B2s (2 vCPU, 4 GiB RAM) is a good starting point.
Step 1 - Deploy from the Azure Marketplace
Sign in to the Azure Portal. Choose Create a resource, search the Marketplace for Puter 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) and HTTP (80). 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:puter-ubuntu-24-04:default:latest
az vm create \
--resource-group <your-rg> \
--name puter \
--image sac1647283583153:puter-ubuntu-24-04:default:latest \
--size Standard_B2s \
--admin-username azureuser \
--generate-ssh-keys \
--public-ip-sku StandardThen open the web interface port on the VM's network security group:
az vm open-port --resource-group <your-rg> --name puter --port 80 --priority 1001Step 3 - Retrieve the per-VM administrator login
A unique administrator account is generated on the first boot of every VM and written to a root-only credentials file. Nothing is shared between deployments, and no default password exists in the image. SSH in and read the file:
ssh azureuser@<vm-public-ip>
sudo cat /root/puter-credentials.txtThe file contains the address, the domain that the appliance serves on, the administrator login, and a ready-made hosts entry.
Keep the password in a safe place. It is the credential for the desktop. You can change it from Account -> Change Password after your first sign in.
Step 4 - Confirm Puter is healthy
The /healthcheck endpoint is deliberately unauthenticated. It is the one route exempt from Puter's Host header validation, so you can probe it from monitoring without any extra headers. Run these commands on the VM:
systemctl is-active puter-s3.service puter.service nginx.service curl -s http://127.0.0.1/healthcheckExpected output:
active active active {"ok":true}Every other route validates the Host header against the configured domain, so a version check must present it:
curl -s -H 'Host: puter.internal' http://127.0.0.1/versionExpected output:
{"version":"2.5.1","major":2,"minor":5,"patch":1,"environment":"prod","location":"sac-node-1","deploy_timestamp":1785054449018}The reported version is the upstream package.json version string. This image is built from the release tag 26.07.1.
Confirm what is listening. nginx holds port 80, the application sits on 4100, and the kernel fences the object store on 4566 to loopback:
ss -tln | grep -E ':80 |:4100|:4566'Expected output:
LISTEN 0 511 0.0.0.0:4566 0.0.0.0:* LISTEN 0 511 0.0.0.0:80 0.0.0.0:* LISTEN 0 511 [::]:80 [::]:* LISTEN 0 511 *:4100 *:*Step 5 - Reach the desktop
Puter is reached by name, never by bare IP address. It routes its API on api.<domain>, hosted sites on site.<domain> and host.<domain>, and private apps on app.<domain> and dev.<domain>, and it reads the configured domain as exactly two labels. A three-label name such as puter.example.com makes it treat puter as a subdomain, and the desktop, sign-in and registration routes all stop matching.
The image therefore ships serving on puter.internal, which is inside the TLD that ICANN reserved for private use. You have two ways to reach it.
Option A - try it immediately, with no DNS at all
Copy the puter.hosts.line value from Step 3 and add it to the hosts file on the machine you browse from. On macOS and Linux, that file is /etc/hosts. On Windows, it is C:\Windows\System32\drivers\etc\hosts. The line looks like this, with your VM's own address:
20.102.81.159 puter.internal api.puter.internal s3.puter.internal site.puter.internal host.puter.internal app.puter.internal dev.puter.internalThen open http://puter.internal/ in your browser.
Option B - serve it on your own domain
Point both example.com and *.example.com at the VM's public address, then run one command on the VM:
sudo puter-set-domain example.comThe helper rewrites the domain, the four hosting subdomains and the browser-facing storage endpoint, and keeps the VM's own name resolution in step. It restarts the service, and then verifies that the desktop answers on the new name before it returns.
The helper refuses anything that is not a two-label domain, because a three-label domain silently breaks subdomain routing instead of failing loudly.
Once TLS is terminated in front of the appliance, re-run the helper as sudo puter-set-domain example.com https.
Step 6 - Sign in
Open the address from Step 5 to reach the sign-in page. Use the username admin and the password from Step 3.
After you sign in, go to /desktop for the desktop environment. Files saved to your Desktop folder appear as icons. The task bar along the bottom holds the launcher, the file manager and the wastebasket.
Step 7 - Work with your files
Open the file manager from the task bar. The sidebar holds Home, Documents, Public, Pictures, Desktop and Videos, and the breadcrumb shows where you are. Each file manager is a desktop window that you can drag and resize. Open a second window alongside it to move files between folders.
Everything you see is stored on this VM under /var/lib/puter, in the signed-in account's own namespace. Folders nest normally, and you can upload files by dragging them into a window.
The Usage page in the account area reports what you are storing. The dollar figures and the Upgrade link belong to the hosted puter.com service and do not apply to your VM.
Step 8 - The security posture
Self-registration is disabled in the shipped configuration before the service ever accepts a request. It is not switched off afterwards. Both permanent signups and Puter's anonymous temporary accounts are refused, and exactly one account can sign in. Prove all three on the VM:
curl -s -o /dev/null -w 'permanent signup: HTTP %{http_code}\n' -H 'Host: puter.internal' \
-H 'Content-Type: application/json' \
-d '{"username":"sacprobe","email":"sacprobe@example.com","password":"Probe-Password-123456","p102xyzname":""}' \
http://127.0.0.1/signup
curl -s -o /dev/null -w 'temporary signup: HTTP %{http_code}\n' -H 'Host: puter.internal' \
-H 'Content-Type: application/json' -d '{"is_temp":true}' http://127.0.0.1/signup
sudo sqlite3 /var/lib/puter/db/puter-database.sqlite \
"SELECT username FROM user WHERE password IS NOT NULL AND password <> '';"Expected output:
permanent signup: HTTP 403 temporary signup: HTTP 403 adminTwo details explain how that state is reached:
- Upstream's first start creates the
adminaccount with a temporary password of its own, and keeps a plain-text copy of it in the database so that later boots can tell whether it has been rotated. This image's first boot replaces that password with a per-VM one, deletes the plain-text copy, and only then starts nginx. The temporary credential is never live on a reachable port. - The JWT signing secrets are generated per VM, so a session minted on one deployment is worthless on another.
To create more accounts, sign in as admin and invite them from the account area. For open registration instead, set "disable_user_signup": false in /etc/puter/config.json and restart puter.service.
Step 9 - Where your data lives
Puter keeps two things on disk: a SQLite database holding accounts, filesystem metadata and permissions, and an object store holding the file contents themselves. Both live under /var/lib/puter, owned by the unprivileged puter service account.
The browser must be able to reach the object store, because Puter hands the browser a presigned URL to upload to. The store is published only through nginx on the s3.<domain> name. The kernel fences the port it listens on to loopback, because the store does no request signing of its own.
The image ships a self-test that exercises exactly the path a browser takes:
sudo /usr/local/sbin/puter-s3-selftest
sudo du -sh /var/lib/puter/db /var/lib/puter/fauxqs-s3-dataExpected output (your byte counts will differ):
S3_SELFTEST_OK presigned upload host s3.puter.internal 784K /var/lib/puter/db 24K /var/lib/puter/fauxqs-s3-dataTaking a backup
Capture the database and the object store together. Metadata without contents, or contents without metadata, restores to nothing. Stop the application first so that the two are consistent:
sudo systemctl stop puter.service
sudo mkdir -p /var/backups/puter
sudo sqlite3 /var/lib/puter/db/puter-database.sqlite ".backup '/var/backups/puter/puter-database.sqlite'"
sudo tar -czf /var/backups/puter/puter-objects.tar.gz -C /var/lib/puter fauxqs-s3-data fauxqs-data
sudo systemctl start puter.service
sudo ls -la /var/backups/puterCopy the resulting files to storage outside the VM. To restore:
- Stop
puter.serviceandputer-s3.service. - Put the database back at
/var/lib/puter/db/puter-database.sqlite, owned byputer:puter. - Remove any stale
-waland-shmfiles beside it. - Extract the object archive back into
/var/lib/puter. - Start both services again.
Step 10 - Enabling HTTPS
The image serves plain HTTP on port 80 so that it works the moment it boots. For anything reachable from the internet, put TLS in front of it.
Puter routes on subdomains, so the certificate must cover the wildcard as well as the bare domain. Point your DNS at the VM's public IP and open 443/tcp on the network security group. Then issue a wildcard certificate with a DNS-01 challenge:
sudo apt-get update && sudo apt-get install -y certbot python3-certbot-nginx
sudo certbot certonly --manual --preferred-challenges dns \
-d example.com -d "*.example.com" \
--agree-tos -m you@example.comInstall the certificate into the nginx server blocks at /etc/nginx/sites-available/sac-puter and /etc/nginx/sites-available/sac-puter-s3, and reload nginx. Then tell Puter that it is served over TLS, so that the origins, redirects and storage URLs it builds all use https:
sudo puter-set-domain example.com httpsStep 11 - Maintenance
Check service health and read the application log:
systemctl is-active puter.service
sudo journalctl -u puter.service -n 20 --no-pager -o catThe image is patched at build time and ships with unattended security updates enabled, so the operating system keeps itself current. To apply updates immediately, run:
sudo apt-get update && sudo apt-get upgrade -yThree units make up the appliance: puter-s3.service (the object store), puter.service (the application) and nginx.service (the reverse proxy, and the only public listener). All three are enabled.
The application does not start until the first-boot bootstrap has written this VM's own secrets. This guard stops it from ever coming up unconfigured after a reboot.
systemctl is-enabled puter-s3.service puter.service nginx.serviceExpected output:
enabled enabled enabledSupport
Every SecureAnyCloud image comes with 24/7 support. Raise a ticket at secureanycloud.com. Include the output of systemctl is-active puter-s3.service puter.service nginx.service and the last 50 lines of sudo journalctl -u puter.service.
Puter is a trademark of Puter Technologies Inc. This image is an unmodified distribution of the Puter open source software and is not affiliated with or endorsed by Puter Technologies Inc.
Deploy on Azure
Launch Puter 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