Applications AWS

wanderer Self-Hosted Trail and GPS Route Database on AWS User Guide

| Product: wanderer

Overview

wanderer is a free, open source, self-hosted database for outdoor routes. You upload GPX tracks or draw trails directly on an interactive map, attach waypoints, photos and notes, and keep your hiking, cycling and running routes together with distance, elevation and other statistics in one place. Trails group into lists, can be kept private or shared, and are searchable instantly across everything you have recorded.

This cloudimg image runs wanderer the officially supported way: the SvelteKit web frontend, a PocketBase backend (a Go binary that embeds an SQLite database and provides your account and route data plus an admin console) and a Meilisearch full-text search engine, all as the upstream release containers pinned by image digest and orchestrated on a private internal network. A single in-network reverse proxy on port 80 is the only public door: / serves the web app and /pb/ serves the PocketBase API. Meilisearch is never published to any host port and PocketBase is published only to the loopback interface for an administrator's SSH tunnel.

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 every instance on first boot, before the app is reachable, and open sign-up is switched off by default. All application data - the database, uploaded GPX tracks and photos, and the search index - lives on a dedicated data volume, separate from the OS disk and independently resizable. 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.

What is included:

  • wanderer v0.20.0 (the AGPL-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 the dedicated data 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)
  • docker.service, wanderer-firstboot.service, wanderer.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 instance on first boot, never baked into the image
  • Open registration disabled by default and the PocketBase admin console kept off the public port
  • A dedicated data volume at /var/lib/wanderer holding the database, GPX uploads, photos and the search index
  • No default login, no shipped secret and an empty database on first boot
  • Ubuntu 24.04 LTS base with the latest security patches applied at build time
  • 24/7 cloudimg support

Prerequisites

An AWS account, an EC2 key pair in the target region, and a VPC with a public subnet. m5.large (2 vCPU / 8 GiB RAM) is the recommended instance type; a larger route library or many concurrent users benefit from more memory. Security group inbound: allow 22/tcp from your management network for SSH and 80/tcp for the web app from the networks that use it.

Step 1 - Launch the AMI

Subscribe to the listing in AWS Marketplace, then launch an instance from the AMI - from the Marketplace Launch action or with the CLI. Attach your key pair and a security group that allows inbound 22/tcp and 80/tcp, and leave the dedicated data volume in the launch template in place.

aws ec2 run-instances \
  --image-id <ami-id> \
  --instance-type m5.large \
  --key-name <your-key-pair> \
  --security-group-ids <security-group-id> \
  --subnet-id <subnet-id> \
  --associate-public-ip-address \
  --metadata-options "HttpTokens=required,HttpEndpoint=enabled" \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=wanderer}]'

The instance uses IMDSv2; the image reads the instance's public IP from instance metadata on first boot to build the app's public URL.

Step 2 - Connect to your instance

Connect over SSH as the default login user for your OS variant.

OS variant Login user SSH command
Ubuntu 24.04 LTS ubuntu ssh ubuntu@<instance-public-ip>

Step 3 - Confirm the services are running

On the first boot of each instance, before the app is reachable, wanderer generates its own per-instance secrets and accounts and brings the stack up. Give the instance a minute, then confirm the four systemd units are active:

sudo systemctl is-active docker wanderer-firstboot wanderer wanderer-postboot
active
active
active
active

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

Step 4 - Review 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 app is public on :80; the 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

Only ports 80 (the proxy) and the loopback-only 127.0.0.1:8091 (the PocketBase admin tunnel) listen; Meilisearch (7700) and the web frontend (3000) are internal to the docker network:

sudo ss -tlnp | grep -E ':80 |8091|7700|3000'
LISTEN 0.0.0.0:80
LISTEN 127.0.0.1:8091

Step 5 - Read your per-instance credentials

Nothing is baked into the image. Read the credentials file (it is 0600 root:root):

sudo cat /root/wanderer-credentials.txt

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.

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

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

Step 6 - Prove 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
OK round-trip: web=http://<instance-public-ip>/ pb=http://<instance-public-ip>.nip.io/pb user=user@wanderer.local created+readback list ag3dgmq07uwdsdw; admin UI blocked 403; unauth create 400

Step 7 - Sign in to the web app

Open http://<instance-public-ip>/ in your browser and sign in with the WANDERER_USER_EMAIL and WANDERER_USER_PASSWORD from the credentials file. The trail library gives you search and filters by category, tags, author, visibility and difficulty, with each trail shown as a card carrying a route thumbnail, location and statistics.

The wanderer trail library with a filter sidebar and a trail card showing a route thumbnail, hiking category, location, difficulty, distance and elevation

Step 8 - Upload a GPX trail

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, estimated duration and elevation profile and reverse-geocodes the location as you go. Open a saved trail to see it on the map with its elevation route profile and statistics.

A single trail rendered on the interactive map over topographic terrain, showing the GPS route and start and end markers

The per-trail statistics bar with distance, duration and elevation gain and loss, the trail description and the elevation route profile computed from the uploaded GPX track, with a mini-map of the route

Step 9 - Browse trails on the map

The Map view browses every stored trail as clustered markers on one interactive map, with a filterable sidebar of trail cards. Use Lists to organise trails into collections and keep each list private or shared.

The interactive map browsing stored trails as clustered markers across a region, with a filterable sidebar of trail cards showing distance, difficulty, category and elevation

Data volume

All application data lives on a dedicated EBS data volume mounted at /var/lib/wanderer, which holds the Docker data root for the stack - the PocketBase SQLite database, the uploaded GPX tracks and trail photos, and the Meilisearch search index. It is separate from the OS disk, so you can resize it independently and back it up with EBS snapshots or AWS Backup as your route library grows.

findmnt -no SOURCE,TARGET,FSTYPE /var/lib/wanderer

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://<instance-public-ip>.nip.io/pb, where nip.io is a public wildcard-DNS service that resolves <instance-public-ip>.nip.io back to <instance-public-ip>. Everything is still served by your instance on port 80; only DNS resolution uses nip.io.

For a production deployment, put wanderer behind your own domain and TLS - for example an Application Load Balancer terminating your certificate. Point a hostname at the instance, 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.

Optional: self-hosted turn-by-turn routing

Map display, GPX upload, trail browsing and search work out of the box against the public OpenStreetMap map, geocoding and routing services. wanderer can additionally use a self-hosted Valhalla routing engine for turn-by-turn route drawing; because Valhalla needs large OpenStreetMap tile extracts, it is not bundled in the appliance. To add it, run a Valhalla instance with the tile data for your region and point VALHALLA_URL in /opt/wanderer/.env at it, then run sudo systemctl restart wanderer.

Managing the service

# view recent logs for a container
sudo 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.