Storage Azure

bewCloud on Ubuntu 24.04 LTS on Azure

| Product: bewCloud on Ubuntu 24.04 LTS

Overview

bewCloud is an open source, self hosted personal cloud. It gives you a clean web interface for storing, browsing and sharing your files, alongside notes, photos, a dashboard, and optional CalDAV and CardDAV calendars and contacts. It speaks standard WebDAV, CalDAV and CardDAV, so your files and calendars sync with the bewCloud desktop and mobile apps or any compatible client, and everything stays on infrastructure you control. It is a deliberately lightweight, privacy friendly alternative to the heavier file sync suites.

The cloudimg image runs bewCloud 4.13.0 from its official container image, with PostgreSQL for its database and a bundled Radicale for CalDAV and CardDAV, all orchestrated with Docker Compose behind nginx on a hardened, fully patched Ubuntu 24.04 LTS base.

The image is deliberately shipped unprovisioned, with no database and no secrets. bewCloud's own documentation notes that, even with sign ups disabled, the first person to sign up becomes an administrator, so on the first boot of every VM a one shot service generates this VM's secrets, migrates a fresh database, and creates a single administrator account with a random password written to a root only file. Until that finishes, nothing is published on port 80 — so no instance is ever reachable from the network in an unprovisioned state where a passer by could claim the admin account, and no secret is ever shared between customers. Backed by 24/7 cloudimg support.

What is included:

  • bewCloud 4.13.0 (Deno) served through nginx, orchestrated by Docker Compose and managed by systemd
  • The bewCloud interface on :80, requiring an administrator sign in
  • A local PostgreSQL 18 database and a bundled Radicale CalDAV/CardDAV server, reachable only inside the VM
  • A per VM database password, application secrets and administrator password generated on first boot and recorded in a root only file
  • Nothing bound to port 80 until first boot has provisioned and secured the instance
  • docker.service and bewcloud.service as enabled systemd units so the stack returns after a reboot
  • 24/7 cloudimg support

Prerequisites

An active Azure subscription, an SSH key pair, and a VNet plus subnet in the target region. Standard_B2s (2 vCPU / 4 GiB RAM) is a sensible starting point for a personal or small team cloud; size up if many users sync large libraries. NSG inbound: allow 22/tcp from your management network and 80/tcp (plus 443/tcp once you terminate TLS on the VM) so your browser and sync clients can reach bewCloud.

Step 1 - Deploy from the Azure Marketplace

Sign in to the Azure Portal, choose Create a resource, search the Marketplace for bewCloud by cloudimg, 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 Review + create and Create.

Step 2 - Deploy from the Azure CLI

az vm create \
  --resource-group <your-rg> \
  --name bewcloud \
  --image <marketplace-image-urn> \
  --size Standard_B2s \
  --admin-username azureuser \
  --generate-ssh-keys \
  --public-ip-sku Standard

Then open port 80 so your browser and sync clients can reach bewCloud:

az vm open-port --resource-group <your-rg> --name bewcloud --port 80

Step 3 - Confirm the services are running

SSH in as azureuser. First boot pulls no images (they are already in the image) and completes in well under a minute; once it has, nginx binds port 80 and bewCloud is ready.

systemctl is-active docker bewcloud
ss -tlnp 2>/dev/null | grep ':80 ' | head -1 | sed 's/  */ /g' | cut -d' ' -f1-4

Confirm the three containers are up and which versions are running:

sudo docker compose -f /opt/bewcloud/docker-compose.yml ps --format 'table {{.Service}}\t{{.Image}}\t{{.Status}}'

The docker and bewcloud systemd units reporting active, nginx bound to port 80, and the bewCloud, PostgreSQL and Radicale containers all up with their pinned image versions

If port 80 is not bound, first boot has not completed. That is deliberate and fail closed: bewCloud is never published before it has been provisioned and secured. Check with systemctl status bewcloud-firstboot.service and journalctl -u bewcloud-firstboot.service.

Step 4 - Retrieve this VM's administrator credentials

Every VM creates its own administrator on first boot and writes the e-mail and password, with the sign in address, to a root only credentials file:

sudo cat /root/bewcloud-credentials.txt

The same first boot also generated this VM's database password and application secrets. You never need those values, but you can confirm they are unique to this machine without printing them, and that exactly one administrator exists:

for k in POSTGRESQL_PASSWORD JWT_SECRET PASSWORD_SALT MFA_KEY MFA_SALT; do v=$(sudo grep -E "^$k=" /opt/bewcloud/.env | cut -d= -f2-); printf "%-20s generated per VM (%d chars)\n" "$k" "${#v}"; done
printf 'administrators on this VM: '; sudo docker compose -f /opt/bewcloud/docker-compose.yml exec -T postgresql psql -U postgres -d bewcloud -tAc "SELECT count(*) FROM public.bewcloud_users WHERE (extra->>'is_admin')::boolean IS TRUE;" </dev/null

The credentials file reported at mode 600 owned by root, each per VM secret confirmed present by length only, and exactly one administrator account on this VM

You can prove the generated password really authenticates, straight from the command line. bewCloud accepts HTTP Basic authentication, so the correct password reaches an authenticated page while a wrong one is bounced back to the sign in screen:

E=$(sudo grep '^bewcloud.admin.email=' /root/bewcloud-credentials.txt | cut -d= -f2-); P=$(sudo grep '^bewcloud.admin.pass=' /root/bewcloud-credentials.txt | cut -d= -f2-); printf 'login page:          HTTP %s\n' "$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1/login)"; printf 'authenticated /files: HTTP %s\n' "$(curl -s -o /dev/null -w '%{http_code}' -u "$E:$P" http://127.0.0.1/files)"; printf 'wrong password:      HTTP %s\n' "$(curl -s -o /dev/null -w '%{http_code}' -u "$E:WRONG-password" http://127.0.0.1/files)"

A 200 for the correct password and a 302 redirect for the wrong one is exactly what you want to see.

The bewCloud login page returning HTTP 200, the per VM administrator password authenticating to the files endpoint with HTTP 200, and a wrong password rejected with an HTTP 302 redirect to the sign in page

Step 5 - Sign in to bewCloud

Browse to http://<vm-public-ip>/. bewCloud opens on its sign in screen — there is no setup wizard, because your administrator was already created on first boot. Enter the bewcloud.admin.email and bewcloud.admin.pass values from Step 4.

The bewCloud sign in page, protected by the administrator account generated on this VM's first boot

Change the password from the settings menu after your first sign in. Sign ups are disabled by default; you add further users deliberately rather than leaving open registration on.

Step 6 - Browse and manage your files

The Files app is the heart of bewCloud. Upload files and create folders from the web interface, browse and search them, and enable sharing per file or per directory. Every file stays on your own server.

The bewCloud file browser showing a file stored on the instance, with the app navigation for files, notes, photos, expenses, contacts and calendar, and the WebDAV URL for this account

The WebDav URL shown on the Files page is what you point a WebDAV client or the bewCloud desktop and mobile apps at, so your files sync across your devices. You can upload straight from the command line with any WebDAV client — here using curl with the per VM administrator credentials:

E=$(sudo grep '^bewcloud.admin.email=' /root/bewcloud-credentials.txt | cut -d= -f2-); P=$(sudo grep '^bewcloud.admin.pass=' /root/bewcloud-credentials.txt | cut -d= -f2-); printf 'Hello from cloudimg.\n' > /tmp/hello.txt; curl -s -o /dev/null -w 'PUT hello.txt -> HTTP %{http_code}\n' -u "$E:$P" -T /tmp/hello.txt http://127.0.0.1/dav/hello.txt

An HTTP 201 (created) confirms the upload; refresh the Files page and the file appears.

Step 7 - Notes, photos, dashboard, and the other apps

bewCloud is more than files. The header switches between the built in apps — a Dashboard with a quick notes widget, a Notes app, Photos, Expenses, and, backed by the bundled Radicale server, Contacts and Calendar. Each app is self contained and stores its data on your instance.

The bewCloud dashboard with its quick notes widget, one of the built in apps available from the header

The bewCloud Notes app, ready for its first note, reached from the app navigation in the header

You can choose which apps appear, and their order, through bewCloud's configuration file at /opt/bewcloud/bewcloud.config.ts (see the bewCloud FAQ); apply changes with sudo docker compose -f /opt/bewcloud/docker-compose.yml restart website.

Step 8 - Check persistence and image pins

The stack is enabled to return after a reboot, and the container images are pinned so your instance is reproducible. Unattended security upgrades keep the base operating system patched:

systemctl is-enabled docker nginx bewcloud-firstboot bewcloud
systemctl is-enabled unattended-upgrades
sudo docker image ls --format '{{.Repository}}:{{.Tag}}' | sort

The docker, nginx, first boot and bewcloud units all enabled, unattended-upgrades enabled, and the three pinned container images for bewCloud, PostgreSQL and Radicale

Step 9 - Enable HTTPS with Let's Encrypt

Put bewCloud behind HTTPS before exposing it to the internet, so credentials and files are encrypted in transit. Point a DNS record at the VM, then install a certificate. The bundled nginx proxies to bewCloud on the loopback interface, so certbot's nginx integration terminates TLS in front of it:

sudo apt-get update && sudo apt-get install -y certbot python3-certbot-nginx
sudo certbot --nginx -d cloud.example.com

After issuing the certificate, set your instance's public address in /opt/bewcloud/bewcloud.config.ts (auth.baseUrl) to the https:// host name and restart the app with sudo docker compose -f /opt/bewcloud/docker-compose.yml restart website, and make sure 443/tcp is open in the NSG.

Step 10 - Back up and maintain your VM

Everything that matters lives in the PostgreSQL database and the uploaded files under /opt/bewcloud/data-files, so a database dump plus that directory is a complete backup:

sudo docker compose -f /opt/bewcloud/docker-compose.yml exec -T postgresql pg_dump -U postgres bewcloud > bewcloud-backup.sql
sudo tar czf bewcloud-files-backup.tar.gz -C /opt/bewcloud data-files

Keep the .env file (/opt/bewcloud/.env) with the dump: it holds the PASSWORD_SALT and application secrets, which are needed to make sense of stored data. Store the backups somewhere safe. Unattended security upgrades keep the operating system patched, and Azure Backup or managed disk snapshots give you whole VM recovery.

Security notes

  • No shared secret ships in the image. The image contains no database and no secrets. The database password, application secrets and administrator password are all generated on the first boot of your VM.
  • The first sign up cannot claim your instance. bewCloud makes the first sign up an administrator; because first boot creates a real administrator before the app is reachable, and sign ups are disabled, no passer by can register the admin account.
  • Nothing is published before it is secured. In the shipped image nothing is bound to port 80. The public site is enabled only after first boot has provisioned bewCloud, so an unprovisioned instance is never exposed to the network.
  • Fail closed. If first boot cannot complete, port 80 is never bound at all.
  • Put bewCloud behind HTTPS before real traffic, and restrict 22/tcp to your management network.
  • The database and Radicale are not exposed. PostgreSQL and Radicale are reachable only inside the VM; nginx on port 80 is the sole network facing service.

Architecture summary

Component Detail
Application bewCloud 4.13.0 (AGPL-3.0), ghcr.io/bewcloud/bewcloud:v4.13.0
Orchestration Docker Compose at /opt/bewcloud, managed by bewcloud.service
Web server nginx reverse proxy on :80 to the loopback bewCloud container
Database PostgreSQL 18, in-VM only
CalDAV/CardDAV Radicale 3.7.6, in-VM only
Configuration /opt/bewcloud/.env (mode 0640) and /opt/bewcloud/bewcloud.config.ts, written on first boot
Credentials /root/bewcloud-credentials.txt, mode 0600, root only
First boot bewcloud-firstboot.service, sentinel /var/lib/cloudimg/bewcloud-firstboot.done
Ports 22/tcp SSH, 80/tcp bewCloud interface, 443/tcp once TLS is configured
Base image Ubuntu 24.04 LTS, fully patched, unattended security upgrades enabled

Support

cloudimg images come with 24/7 support. Email support@cloudimg.co.uk with your Azure subscription ID and the VM name. For bewCloud itself, see the bewCloud project on GitHub and its FAQ.