Wa
Applications Azure

wanderer on Ubuntu 24.04 on Azure User Guide

| Product: wanderer on Ubuntu 24.04 LTS on Azure

Overview

This guide covers the deployment and configuration of wanderer on Ubuntu 24.04 on Azure using cloudimg Azure Marketplace images. wanderer is an open source, self-hosted database for outdoor routes. You upload or draw trails on an interactive map, attach GPX tracks, waypoints, photos and notes, and keep your hiking, cycling and running routes together with distance, elevation and other stats in one place. Trails group into lists, can be kept private or shared, and are searchable instantly across everything you have recorded. It pairs a SvelteKit web frontend with PocketBase (a single Go binary that embeds an SQLite database) for your account and route data, and Meilisearch for fast full-text search.

The cloudimg image ships the free and open source, GPL-3.0 licensed wanderer application, run the officially supported way as the upstream containers pinned by image digest, alongside the MIT licensed Meilisearch engine. All images are captured into the VM, so your instance starts in seconds. Because the upstream project ships with open registration enabled and its search and backend keys set to public example values, nothing here ships with a known secret: a unique Meilisearch master key, a unique PocketBase encryption key, a per instance app account and a separate backend superuser, each with a random password, are generated for each VM on first boot, before the app is reachable, and open sign-up is switched off by default. Backed by 24/7 cloudimg support.

wanderer, PocketBase and Meilisearch are trademarks of their respective owners. This image is produced by cloudimg and is not affiliated with, endorsed by, or sponsored by any of them. It ships the free and open source self-hosted software, unmodified.

The docker, wanderer-firstboot, wanderer and wanderer-postboot services all active, and the four compose containers - PocketBase (db), the nginx proxy, Meilisearch (search) and the SvelteKit web frontend - running and healthy, with PocketBase and Meilisearch bound to loopback / the internal network only and the proxy the single public door on port 80

What is included:

  • wanderer v0.20.0 (the GPL-3.0 licensed SvelteKit frontend + PocketBase backend), pinned by image digest
  • Meilisearch v1.36.0 (the MIT licensed search engine) in production mode, so every request requires a key
  • PocketBase's embedded SQLite database, created empty on first boot and stored on a persistent volume
  • Docker Engine with the web frontend, PocketBase and Meilisearch on a private network, fronted by an in-network nginx proxy that is the only container published to the host, on port 80 (/ = web app, /pb/ = PocketBase API)
  • wanderer.service, wanderer-firstboot.service and wanderer-postboot.service as systemd units, enabled and active on boot
  • A unique Meilisearch master key, a unique PocketBase encryption key, a per instance app account and a separate backend superuser, each with a random password generated per VM on first boot, never baked into the image
  • Open registration disabled by default and the PocketBase admin console kept off the public port
  • No default login, no shipped secret and an empty database on first boot
  • Ubuntu 24.04 LTS base with latest security patches applied at build time
  • Azure Linux Agent for seamless cloud integration and SSH key injection
  • 24/7 cloudimg support with guaranteed 24 hour response SLA

Prerequisites

  • Active Azure subscription, SSH public key, VNet + subnet in target region
  • Subscription to the wanderer listing on Azure Marketplace

Recommended virtual machine size: Standard_B2s (2 vCPU, 4 GB RAM) is a sensible starting point for a person, a household or a small club. For larger route libraries or many concurrent users use Standard_D2s_v5 or larger. NSG inbound: allow 22/tcp from your management network and 80/tcp for the web app from the networks that use it.

Step 1: Deploy from the Azure Portal

Sign in to the Azure Portal, choose Create a resource, search the Marketplace for wanderer 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

RG="wanderer-prod"; LOCATION="eastus"; VM_NAME="wanderer-01"
GALLERY_IMAGE_ID="/subscriptions/<sub-id>/resourceGroups/azure-cloudimg/providers/Microsoft.Compute/galleries/cloudimgGallery/images/wanderer-ubuntu-24-04/versions/<version>"
SSH_KEY="$(cat ~/.ssh/id_rsa.pub)"
az group create --name "$RG" --location "$LOCATION"
az network vnet create -g "$RG" --name wanderer-vnet --address-prefix 10.100.0.0/16 --subnet-name wanderer-subnet --subnet-prefix 10.100.1.0/24
az network nsg create -g "$RG" --name wanderer-nsg
az network nsg rule create -g "$RG" --nsg-name wanderer-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 wanderer-nsg --name allow-http --priority 110 \
  --destination-port-ranges 80 --access Allow --protocol Tcp
az vm create -g "$RG" --name "$VM_NAME" --image "$GALLERY_IMAGE_ID" \
  --size Standard_B2s --storage-sku StandardSSD_LRS \
  --admin-username azureuser --ssh-key-values "$SSH_KEY" \
  --vnet-name wanderer-vnet --subnet wanderer-subnet --nsg wanderer-nsg --public-ip-sku Standard

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

Step 3: First boot and per-instance credentials

On the first boot of each VM, before the app is reachable, wanderer generates its own per-instance secrets and accounts. Nothing is baked into the image. Give the instance a minute after first boot, then SSH in and read the credentials file (it is 0600 root:root):

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

The per instance credentials file, showing the wanderer app login and the separate PocketBase backend superuser, each with a per VM random password (masked here), the per VM Meilisearch master key and PocketBase encryption key, and the resolved web and PocketBase URLs - none of these values is shipped in the image

The file contains two accounts, both with per-instance random passwords:

  • WANDERER_USER_EMAIL / WANDERER_USER_PASSWORD — the app login you use in the web UI.
  • PB_SUPERUSER_EMAIL / PB_SUPERUSER_PASSWORD — the PocketBase backend superuser for administering collections. Its admin console is not exposed on the public port (see the security model below).

Confirm the services and containers are healthy:

sudo systemctl is-active docker wanderer-firstboot wanderer wanderer-postboot
active
active
active
active
docker compose -f /opt/wanderer/docker-compose.yml ps

The db (PocketBase) and search (Meilisearch) containers publish to loopback / the internal network only; the nginx proxy container is the single public door on port 80.

Step 4: The security model

cloudimg hardens every instance before the port is ever public. The web app, PocketBase and Meilisearch run as containers on a private docker network; only the nginx proxy is published, on port 80, routing / to the web app and /pb/ to the PocketBase API. The PocketBase admin console (/pb/_/) is blocked at the proxy and returns 403, and Meilisearch is never published to any host port. Open user registration is disabled by default (PUBLIC_DISABLE_SIGNUP=true).

The security posture: PocketBase published only to 127.0.0.1:8091, the web frontend and Meilisearch on internal container ports, the nginx proxy the only 0.0.0.0:80 listener, the public app returning 200, the PocketBase API health returning 200, the admin UI blocked with 403 and an unauthenticated create rejected with 400

# the app is public on :80; PocketBase API is reachable for the browser via /pb/
curl -s -o /dev/null -w "app / -> %{http_code}\n" http://127.0.0.1/
curl -s -o /dev/null -w "pb  /pb/api/health -> %{http_code}\n" http://127.0.0.1/pb/api/health
# the admin console is NOT public
curl -s -o /dev/null -w "admin /pb/_/ -> %{http_code}\n" http://127.0.0.1/pb/_/
app / -> 200
pb  /pb/api/health -> 200
admin /pb/_/ -> 403

To reach the PocketBase admin console for backend administration, tunnel to the loopback container port over SSH and log in with the superuser credentials:

ssh -L 8091:127.0.0.1:8091 azureuser@<vm-ip>
# then open http://localhost:8091/_/ in your browser

Step 5: Verify the end-to-end round-trip

The image ships a prover that authenticates as the per-instance app account, creates a trail list through the public front door, reads it back, and confirms an unauthenticated create is rejected:

sudo /usr/local/sbin/wanderer-roundtrip.sh

The credential round-trip prover reporting OK: the per VM app user authenticated, created a trail list and read it back through the nginx proxy, the admin UI is blocked with 403 and an unauthenticated create was rejected with 400

Step 6: Sign in to the web app

Open http://<vm-ip>/ in your browser and sign in with the WANDERER_USER_EMAIL and WANDERER_USER_PASSWORD from the credentials file.

The wanderer sign-in page, asking for a username or email and a password - the per instance app account created on first boot

Once signed in, the home view shows your activity feed of trails and lists, each with its recorded distance, elevation gain and loss, and duration.

The signed-in wanderer home feed showing recorded trails - a Lakeside Morning Run with 6.40 km distance, 40 minutes duration and 35 m of elevation gain, and a Favourite Routes list - proving the trail database is live and populated

Choose New Trail to open the route planner. You can Draw a route directly on the interactive map or Upload file to import a GPX track, and wanderer computes the distance, duration and elevation profile as you go.

The wanderer route planner: an interactive OpenStreetMap map with Draw a route and Upload file options, and a Basic Info panel with distance, estimated duration, elevation gain and loss fields that populate as a route is drawn or imported

Use Lists to organise trails into collections - for example a set of favourite routes - and keep each list private or share it.

The wanderer Lists view showing two trail collections alongside the map, demonstrating that trails can be grouped into shareable lists

A note on the PocketBase URL and using your own domain

wanderer's web frontend talks to PocketBase from both the browser and its server-side rendering using the same address. To keep those two paths correct on a plain IP deployment, the image sets the browser-facing PocketBase URL to http://<vm-ip>.nip.io/pb, where nip.io is a public wildcard-DNS service that resolves <vm-ip>.nip.io back to <vm-ip>. Everything is still served by your VM on port 80; only DNS resolution uses nip.io.

For a production deployment you should put wanderer behind your own domain and TLS. Point a hostname at the VM, set ORIGIN (the app) to https://wanderer.example.com and PUBLIC_POCKETBASE_URL to https://pb.example.com/pb (a distinct host from ORIGIN), in /opt/wanderer/.env, and run sudo systemctl restart wanderer.

Managing the service

# view recent logs for a container
docker compose -f /opt/wanderer/docker-compose.yml logs --tail 50 web
# restart the whole stack
sudo systemctl restart wanderer

Support

cloudimg provides 24/7 support with a guaranteed 24 hour response SLA. Contact support@cloudimg.co.uk for assistance with this image.