Tr
Applications Azure

TREK on Ubuntu 24.04 on Azure User Guide

| Product: TREK 3.4.1 on Ubuntu 24.04 LTS on Azure

Overview

TREK is an open source, self-hosted planner for real trips. You build a day by day itinerary, save places onto an interactive map, record accommodation and transport, keep tickets and confirmations together as documents, and track what the trip costs — including splitting expenses across everyone travelling. Anyone you invite to a trip works on the same plan at the same time: notes, polls and chat update live over a WebSocket, so a group can settle an itinerary without emailing spreadsheets around.

The cloudimg image builds TREK 3.4.1 from the official upstream source release (git tag v3.4.1, commit a0994658) using upstream's own workspace build, and then wraps it as a proper appliance. The single Node.js 24 process that serves both the API and the React client is bound to loopback only, and nginx on port 80 is the only public listener — with the WebSocket upgrade explicitly configured, because TREK's real time features fail silently behind a proxy that does not pass Upgrade and Connection headers.

Two security decisions are worth stating plainly, because they differ from a stock install:

  • Public sign-up ships CLOSED. Upstream enables it by default: registration is only refused when the password_registration setting is exactly false, and an absent setting means enabled. On a stock instance anyone who reaches the address can create an account. This image closes that at first boot. Invite links still work — that is the supported way to add people — and you can re-open public sign-up from Admin → Settings whenever you want it.
  • Every secret is generated per virtual machine at first boot, never baked into the image: the at-rest encryption key, the session signing secret, and a one-time administrator password written to a root-only credentials file. The application and nginx are both held closed by a bootstrap marker until those exist, so there is never a window in which a fresh instance is reachable with an empty user table.

Because TREK is licensed under the AGPL-3.0, the exact upstream source this image was built from travels with it, checksummed, together with the licence and a written offer. Backed by 24/7 cloudimg support.

What is included:

  • TREK 3.4.1 built from the official v3.4.1 source release, pinned by commit and verified to declare version 3.4.1
  • Node.js 24 (matching upstream's own build and runtime), SQLite via better-sqlite3 in WAL mode
  • The application bound to 127.0.0.1:3000, with nginx on :80 as the sole public listener
  • Explicit WebSocket proxying on /ws (Upgrade/Connection headers, 24 hour read timeout) so live collaboration works out of the box
  • A 500 MB request body limit in nginx, so backup restore uploads are not rejected
  • Per-VM at-rest encryption key, session signing secret and administrator password generated at first boot
  • Public sign-up closed by default, with invite links and OIDC single sign-on still available
  • A pre-start guard that refuses to run the application on an empty, placeholder or known-default encryption key
  • Booking auto-import enabled (the kitinerary extractor reads PDF and calendar confirmations)
  • SMTP, OIDC and Unsplash credentials shipped empty for you to supply
  • The complete AGPL-3.0 corresponding source at /opt/trek/SOURCE/, checksummed, with the licence and a written offer
  • Security updates via unattended-upgrades
  • 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 the recommended size and is comfortable for a household or small group. NSG inbound: allow 22/tcp from your management network and 80/tcp for the web interface. Do not open 3000/tcp — nothing listens on it externally by design.

TREK renders map tiles, searches for places and fetches trip cover images from public third-party services, so the VM needs outbound HTTPS (443) for the map and place search to work. No API key is required for the default map tiles.

Step 1 - Deploy from the Azure Marketplace

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

Step 2 - Deploy from the Azure CLI

az vm create \
  --resource-group <your-rg> \
  --name trek \
  --image cloudimg:trek-ubuntu-24-04:default:latest \
  --size Standard_B2s \
  --storage-sku StandardSSD_LRS \
  --admin-username azureuser \
  --generate-ssh-keys \
  --public-ip-sku Standard

az vm open-port --resource-group <your-rg> --name trek --port 80 --priority 1010

Restrict 22/tcp to your own management range rather than leaving it open to the internet:

az network nsg rule create --resource-group <your-rg> --nsg-name trekNSG \
  --name allow-ssh-mgmt --priority 1000 --access Allow --protocol Tcp \
  --destination-port-ranges 22 --source-address-prefixes <your-mgmt-cidr>

Step 3 - Confirm first boot completed

At first boot a one-shot service generates this VM's own secrets, creates the administrator account, closes public sign-up, and only then writes the marker that allows the application and nginx to start. Give it about a minute after the VM reports running, then check:

sudo systemctl is-active trek.service nginx.service trek-firstboot.service

All three report active. The bootstrap service is a oneshot that stays active (exited) after succeeding, which is why it reads active rather than inactive.

Confirm what is listening. The application is on loopback only; nginx is the public listener:

sudo ss -tlnp | grep -E ':(80|3000)\b' | awk '{print $1, $4}'

TREK services active and only nginx listening publicly

The bootstrap writes a full account of what it did to the journal:

sudo journalctl -u trek-firstboot.service --no-pager | grep -oE 'trek-firstboot: .*' | tail -12

Step 4 - Retrieve the administrator password

The per-VM credentials are written to a root-only file. No password is ever printed to the journal.

sudo cat /root/trek-credentials.txt

You will see the instance URL, the administrator e-mail, and a one-time password:

First boot bootstrap journal, the per VM credentials file with the password redacted, and file modes

The file is 0600 root:root, and the database and both secret files are 0600 owned by the unprivileged trek service account. Verify:

sudo stat -c '%n %a %U:%G' /root/trek-credentials.txt /etc/trek/trek.env \
  /opt/trek/server/data/travel.db /opt/trek/server/data/.jwt_secret

TREK forces a password change at first sign-in. The password above is deliberately a one-time value — sign in with it and choose your own immediately.

Step 5 - Check the application is healthy

An unauthenticated health endpoint and the public app-config endpoint confirm the version and the security posture:

curl -s http://127.0.0.1/api/health; echo
curl -s http://127.0.0.1/api/auth/app-config \
  | python3 -c "import json,sys; d=json.load(sys.stdin); print(json.dumps({k:d.get(k) for k in ('version','has_users','password_registration','demo_mode')}, indent=2))"

version is 3.4.1, has_users is true (the administrator exists), password_registration is false (public sign-up is closed) and demo_mode is false.

Prove for yourself that a passer-by cannot enrol:

curl -s -o /dev/null -w 'POST /api/auth/register -> HTTP %{http_code}\n' \
  -X POST http://127.0.0.1/api/auth/register -H 'Content-Type: application/json' \
  --data '{"username":"passerby","email":"passer@by.invalid","password":"Passerby-Pw-2026"}' 2>/dev/null

403 — password registration is disabled.

Health check, reported version 3.4.1, and a registration attempt refused with HTTP 403

Sign-in and registration share a rate limit of 10 attempts per 15 minutes per client address. If you see 429 rather than 403, wait for the window to clear — a 429 is a rate limit, not a refusal.

Step 6 - Sign in

Browse to http://<TREK_URL>/ (the address is recorded as trek.url in the credentials file). Sign in with the e-mail and one-time password from Step 4.

TREK sign in page showing the e-mail and password form

TREK will require you to set a new password immediately. On first sign-in you will also see a one-time thank-you note from TREK's author with links to support the project — that is genuine upstream behaviour; dismiss it and it will not return.

You land on My Trips, with a hero card for your next trip, an Atlas of countries visited, and a New Trip button:

TREK trips dashboard showing an upcoming trip with its dates, traveller count and saved destinations

Step 7 - Build a day by day itinerary

Create a trip with New Trip, giving it a title and start and end dates — TREK generates one day per date. Open the trip and use Add Place/Activity to save places; each one can carry an address, a time, a duration, a cost and notes. Drag a saved place onto a day to schedule it, and the day rolls up its own subtotal while the trip shows a running total.

TREK trip planner with a day by day itinerary on the left, saved places on the right, and the route on an interactive map

The tabs across the top are the rest of the trip: Transports for flights and trains, Book for reservations, Lists for packing and to-dos, Costs for the budget, Files for documents, and Collab for shared notes, polls and chat.

The map, place search and cover-image search call public third-party services, so they need outbound HTTPS from the VM. The default tiles need no API key. To point the map at your own tile server, or to add an Unsplash key for cover images, use Settings → Map and Admin → Settings.

Step 8 - Track and split trip costs

Costs records expenses against the trip, groups them by day and category, and settles up who owes whom once you record who paid:

TREK costs view showing total trip spend, categorised expenses and a settle up summary

Step 9 - Add the people you are travelling with

Public sign-up is closed on this image, so add collaborators deliberately rather than letting anyone register:

  • Invite linksAdmin → Users and Invites creates a link with a use count and an expiry. Invited people register through that link even while public sign-up is closed. This is the recommended route.
  • Guests — a trip can include accountless participants you assign items and split costs to, who never sign in at all.
  • Re-open public sign-up — if you genuinely want open registration (a private network, say), turn it back on in Admin → Settings.

Confirm the invite endpoint is live (an unknown token is correctly rejected):

curl -s -o /dev/null -w 'unknown invite token -> HTTP %{http_code}\n' \
  http://127.0.0.1/api/auth/invite/not-a-real-token 2>/dev/null

Step 10 - Real time collaboration over WebSocket

TREK's shared notes, polls, chat and live presence run over a WebSocket at /ws. nginx is configured to upgrade that path — without the Upgrade and Connection headers the live features fail silently while every ordinary HTTP request still returns 200, so it is worth knowing how to check.

The handshake completes and TREK then closes the socket with application code 4001 when no session token is supplied, which is exactly the expected answer for an unauthenticated probe:

cd /opt/trek && node -e '
const WebSocket = require("ws");
const ws = new WebSocket("ws://127.0.0.1/ws");
let upgraded = false;
const t = setTimeout(() => { console.log("timed out, upgraded=" + upgraded); process.exit(0); }, 10000);
ws.on("upgrade", () => { upgraded = true; });
ws.on("close", (c) => { clearTimeout(t); console.log("HTTP upgrade ok=" + upgraded + ", close code=" + c); process.exit(0); });
ws.on("error", () => { clearTimeout(t); console.log("upgrade failed, upgraded=" + upgraded); process.exit(0); });'

HTTP upgrade ok=true, close code=4001 means the proxy and the real time layer are both working.

Step 11 - Import bookings from confirmations

The image ships the kitinerary extractor, so TREK can read a PDF or calendar booking confirmation and turn it into a reservation. In a trip, open Book and upload the confirmation. Confirm the extractor is present:

grep -E '^KITINERARY_EXTRACTOR_PATH=' /etc/trek/trek.env

Step 12 - Terminate TLS for production

The image serves plain HTTP on port 80, because no certificate can be issued for an address that does not exist until you create the VM. Put TLS in front before you use this for anything real — TREK marks its session cookie Secure when it knows the connection is HTTPS, installable progressive web app support requires HTTPS, and OIDC single sign-on requires it.

So that browser sign-in works out of the box over plain HTTP, the image ships COOKIE_SECURE=false. Once you have HTTPS in front, remove that line so the session cookie is marked Secure again:

sudo apt-get update && sudo apt-get install -y certbot python3-certbot-nginx
sudo certbot --nginx -d <your-domain> -m <your-email> --agree-tos --no-eff-email

# Then harden the cookie and tell TREK its public URL:
sudo sed -i '/^COOKIE_SECURE=/d' /etc/trek/trek.env
sudo sed -i 's@^APP_URL=.*@APP_URL=https://<your-domain>@' /etc/trek/trek.env
sudo sed -i 's@^ALLOWED_ORIGINS=.*@ALLOWED_ORIGINS=https://<your-domain>@' /etc/trek/trek.env
echo 'FORCE_HTTPS=true' | sudo tee -a /etc/trek/trek.env
sudo systemctl restart trek.service nginx.service

TRUST_PROXY=1 is already set, which is what lets TREK see the original protocol behind the proxy.

Step 13 - Configure e-mail notifications

SMTP ships empty. Trip reminders and notification e-mails need it:

sudo tee -a /etc/trek/trek.env >/dev/null <<'ENVEOF'
SMTP_HOST=<your-domain>
SMTP_PORT=587
SMTP_USER=<your-email>
SMTP_PASS=<your-token>
SMTP_FROM=<your-email>
ENVEOF
sudo systemctl restart trek.service

Step 14 - Back up your data

Everything TREK stores lives in two directories plus two secret files. Back up the encryption key with the data — without it, stored secrets such as API keys and one-time-password seeds cannot be decrypted.

sudo ls -1 /opt/trek/server/data /opt/trek/server/uploads

TREK also has its own backup feature in Admin → Backups, which produces a restorable archive of the database and uploads. nginx is configured with a 500 MB body limit so restoring a large archive through the browser works.

Step 15 - The AGPL-3.0 source on this image

TREK is licensed under the GNU Affero General Public License v3.0. Because people interact with it over a network, section 13 requires that the corresponding source be available to them. The complete, unmodified upstream source this image was built from ships on the image, with its checksum and a written offer:

ls -lh /opt/trek/SOURCE/ && cd /opt/trek/SOURCE && sha256sum -c trek-3.4.1.tar.gz.sha256

The AGPL-3.0 corresponding source archive on the image with its checksum verifying and the licence header

sudo head -20 /opt/trek/SOURCE/WRITTEN-OFFER.txt

If you run this as a network service, that archive is what you make available to your users. No modification has been made to the TREK source in this image — cloudimg applies deployment configuration only.

Maintenance

Service control and logs

sudo systemctl status trek.service --no-pager | head -12
sudo journalctl -u trek.service -n 20 --no-pager | tail -12

TREK also writes an application log to /opt/trek/server/data/logs/.

Configuration lives in /etc/trek/trek.env (0640 root:trek). It is a systemd EnvironmentFile, so keep values unquoted and on one line, and restart trek.service after editing. LOG_LEVEL=debug gives verbose admin-level detail.

Upgrading TREK. This image pins 3.4.1. Take a backup first (Admin → Backups), then follow upstream's release notes. cloudimg publishes refreshed images as upstream releases land, so the simplest upgrade path is a new VM from a newer image with your backup restored into it.

Rotating the encryption key is supported by upstream's own script; read /opt/trek/wiki/Encryption-Key-Rotation.md on the image first, and back up before starting.

The in-app help at /help is served from /opt/trek/wiki on the image, so it always matches the version you are running rather than the latest upstream docs.

Troubleshooting

Sign-in appears to succeed then immediately fails, or the app says "Access token required". The session cookie is being dropped. This happens when TREK believes the connection is HTTPS but it is not, or the reverse. Over plain HTTP COOKIE_SECURE=false must be present in /etc/trek/trek.env; behind TLS it must be absent and TRUST_PROXY=1 present.

429 Too many attempts. Sign-in, registration and invite lookups allow 10 attempts per 15 minutes per client address. Wait for the window, or restart trek.service — the counter is in memory.

The map is blank or place search finds nothing. Both need outbound HTTPS from the VM. Check your NSG and any egress firewall. Map tiles need no API key; if your network blocks the public tile hosts, point Settings → Map at a tile server you can reach.

The application will not start. The pre-start guard refuses to run TREK on a missing bootstrap marker or an unset, placeholder or known-default encryption key. Its reason is in the journal:

sudo journalctl -u trek.service -n 15 --no-pager | tail -8

Locked out of the administrator account. Upstream ships a recovery script on the image:

sudo -u trek RESET_ADMIN_EMAIL=<your-email> RESET_ADMIN_PASSWORD=<new-password> \
  node /opt/trek/server/reset-admin.js

Support. cloudimg support is available 24/7 at support@cloudimg.co.uk.


TREK is free software licensed under the GNU Affero General Public License v3.0. cloudimg is not affiliated with, endorsed by, or sponsored by the TREK project; the TREK name is used only to identify the unmodified open source software distributed in this image. The corresponding source ships at /opt/trek/SOURCE/.