Deployment guide

AirTrail

Overview

AirTrail is an open source, self-hosted flight tracker and travel log. You record each flight with its route, airline, aircraft and seat, and AirTrail draws every journey as a great-circle arc on an interactive world map. It also keeps running statistics for distance flown, time in the air, airports and countries visited, airlines and aircraft types.

This SecureAnyCloud image runs AirTrail on Ubuntu 24.04 on Azure. It ships the free and open source, GPL-3.0 licensed application as the upstream container pinned by image digest, the officially supported way to run it, with PostgreSQL 16 as its database. Both container images are captured into the VM, so your instance starts in seconds.

Upstream AirTrail makes the first visitor to a new instance its owner, which is a real risk for a VM with a public IP. This image closes that window completely. On first boot, before the public port is ever opened, the instance generates a per-VM PostgreSQL password and claims the owner account itself with a per-VM random password, written to a root-only file.

What you get:

  • AirTrail v3.11.1 (the GPL-3.0 licensed SvelteKit flight tracker), pinned by image digest
  • Flights added by hand, looked up from a flight number or imported in bulk, with several people sharing one instance, each with their own account and map
  • PostgreSQL 16, created empty on first boot and stored on a persistent volume, reachable only from the application container
  • Docker Engine running both containers on a private network, with the app published to loopback only
  • Host nginx as the single public listener on port 80, with header buffers sized for authenticated responses
  • airtrail.service, airtrail-firstboot.service and airtrail-postboot.service as systemd units, enabled and active on boot
  • A per-VM PostgreSQL password and a per-VM owner account password, generated on first boot and never baked into the image
  • The owner account claimed during bootstrap while port 80 is still closed, so a passer-by can never claim the instance
  • No open self-registration: the owner creates further accounts from Settings
  • No default login, no shipped secret, no bundled third-party API key, and an empty database on first boot
  • Ubuntu 24.04 LTS base with the latest security patches applied at build time
  • Azure Linux Agent for cloud integration and SSH key injection
  • 24/7 SecureAnyCloud support with a 24-hour response SLA

AirTrail is a trademark of its respective owner. This image is produced by SecureAnyCloud and is not affiliated with, endorsed by, or sponsored by the AirTrail project. It ships the free and open source self-hosted software, unmodified.

Prerequisites

  • An active Azure subscription, an SSH public key, and a VNet with a subnet in the target region
  • A subscription to the AirTrail listing on Azure Marketplace

Recommended virtual machine size: Standard_B2s (2 vCPU, 4 GB RAM). It comfortably exceeds AirTrail's stated minimum of 2 CPU cores, 2 GB RAM and 10 GB disk, and suits a person, a household or a small team. For many concurrent users, use Standard_D2s_v5 or larger.

Configure the network security group (NSG) to allow inbound 22/tcp from your management network and 80/tcp for the web app from the networks that use it.

1

Deploy from the Azure Portal

Sign in to the Azure Portal, choose Create a resource, search the Marketplace for AirTrail by SecureAnyCloud, and select Create.

On the Basics tab, 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).

Select Review + create, then Create.

2

Deploy from the Azure CLI

RG="airtrail-prod"; LOCATION="eastus"; VM_NAME="airtrail-01" IMAGE_URN="sac1647283583153:airtrail-ubuntu-24-04:default:latest" SSH_KEY="$(cat ~/.ssh/id_rsa.pub)" az group create --name "$RG" --location "$LOCATION" az network vnet create -g "$RG" --name airtrail-vnet --address-prefix 10.100.0.0/16 --subnet-name airtrail-subnet --subnet-prefix 10.100.1.0/24 az network nsg create -g "$RG" --name airtrail-nsg az network nsg rule create -g "$RG" --nsg-name airtrail-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 airtrail-nsg --name allow-http --priority 110 \
  --destination-port-ranges 80 --access Allow --protocol Tcp
# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn sac1647283583153:airtrail-ubuntu-24-04:default:latest
az vm create -g "$RG" --name "$VM_NAME" --image "$IMAGE_URN" \
  --size Standard_B2s --storage-sku StandardSSD_LRS \
  --admin-username azureuser --ssh-key-values "$SSH_KEY" \
  --vnet-name airtrail-vnet --subnet airtrail-subnet --nsg airtrail-nsg --public-ip-sku Standard

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

3

First boot and per-instance credentials

On the first boot of each VM, AirTrail generates its own per-instance secrets and claims its own owner account before the web port is opened. Nothing is baked into the image.

Wait a minute after first boot, then SSH in and read the credentials file (mode 0600 root:root):

sudo cat /root/airtrail-credentials.txt

The file contains everything unique to this instance:

  • AIRTRAIL_USERNAME and AIRTRAIL_PASSWORD: the owner login you use in the web UI. The owner has full access, including user management.
  • AIRTRAIL_URL: the web address of this instance, resolved from its public IP on first boot.
  • POSTGRES_PASSWORD: the database role password. PostgreSQL is not published to any host port, so this password is used only inside the private container network.

Confirm that the services and containers are healthy:

sudo systemctl is-active docker airtrail-firstboot airtrail airtrail-postboot nginx
active active active active active
sudo docker compose -f /opt/airtrail/docker-compose.yml ps --format 'table {{.Service}}\t{{.Status}}\t{{.Ports}}'
SERVICE STATUS PORTS airtrail Up 8 minutes (healthy) 127.0.0.1:3000->3000/tcp db Up 8 minutes (healthy) 5432/tcp

The application container publishes to loopback only, and PostgreSQL has no host port at all. Host nginx on port 80 is the single public entry point.

4

The security model

Upstream AirTrail lets the first person who opens a new instance create the owner account. On a VM with a public IP that is a real risk, so SecureAnyCloud removes it in two independent ways.

First, airtrail-postboot.service claims the owner account itself, over the loopback port, with a password generated for this VM. AirTrail's own setup endpoint then refuses all further attempts with Owner already exists. The application code enforces this, not a configuration flag.

Second, host nginx, the only public listener, does not start until the owner exists. It is gated on a bootstrap-ready marker that the post-boot service creates only after the owner account is in place. Port 80 is therefore closed for the whole bootstrap window.

The database password is also generated on first boot, before the stack can start. The upstream sample value (DB_PASSWORD=<set-at-first-boot>) never exists on your VM. A start-up guard refuses to launch the application at all if the password is empty, too short or a known default.

ss -tln | grep -E ':(80|3000|5432) ' | awk '{print $1, $4}'
LISTEN 127.0.0.1:3000 LISTEN 0.0.0.0:80 LISTEN [::]:80

Cross-site form submissions are rejected, and anonymous visitors cannot read map data that belongs to a signed-in user:

curl -s -o /dev/null -w 'form POST without an Origin header -> %{http_code}\n' -X POST http://127.0.0.1/api/users/setup --data-urlencode username=probe --data-urlencode password=probeprobe1 --data-urlencode displayName=probe
curl -s -o /dev/null -w 'authenticated-only map API (anonymous) -> %{http_code}\n' http://127.0.0.1/api/map-styles/openaip/tiles/1/1/1
form POST without an Origin header -> 403 authenticated-only map API (anonymous) -> 401

A fresh instance has exactly one account, the owner:

sudo docker exec airtrail-db psql -w -U airtrail -d airtrail -tAc "SELECT username, role FROM \"user\"" </dev/null
admin|owner
5

Verify the end-to-end round-trip

Run the prover script that ships with the image. It exercises the whole security model through the public front door. It confirms that:

  • The app answers
  • Anonymous visitors are sent to the login page
  • Cross-site form posts are refused
  • The owner seat is taken
  • A wrong password is rejected
  • The per-instance password logs in and reads back real authenticated data
sudo /usr/local/sbin/airtrail-roundtrip.sh
6

Sign in to the web app

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

The map is the home view once you sign in. Every flight you log is drawn as a great-circle arc between its two airports, so your travel history builds into a picture of where you have been. The controls on the right zoom, re-centre, switch the base map and filter which flights are shown. The dock along the bottom opens the flight list, statistics and settings.

Choose Statistics in the dock for the totals AirTrail derives from your logged flights. They include the number of flights, distance flown as a fraction of the way around the globe, total time in the air, and how many airports and countries you have visited. You also get breakdowns by seat class, cabin, trip reason, continent and airline.

List flights shows every flight in date order with its airline, flight number, route, duration and seat. To record a new flight, choose Add flight in the dock. You can enter it by hand or look it up by flight number and date.

Adding other people

This image has no open self-registration, so nobody can create an account on your instance without you. To add a family member or colleague, sign in as the owner, open Settings from the dock, choose Users, and add the account there.

Each person gets their own flights, their own map and their own statistics.

Map data and optional integrations

The base map is served by CARTO's public basemap CDN, and airport locations come from a map data file baked into the image. Neither needs an API key, and no map key is shipped or required.

The browser fetches the base map tiles from the public internet. A client with no outbound internet access sees the flight arcs over an empty background instead of a map.

Two optional integrations are deliberately left unset. Supply a key for either one if you want it:

  • OpenAIP adds an aeronautical overlay (airspaces, navaids, reporting points). Without a key, that overlay returns "not configured" and the rest of the map is unaffected.
  • AeroDataBox enriches flight lookup by flight number.

Add either key from Settings in the web UI. No third-party key is baked into this image.

Using your own domain

On first boot, the instance sets its own address, ORIGIN, to http://<vm-ip>. AirTrail uses that value to decide which form submissions to trust. If you reach the VM by a DNS name instead of its IP address, you must update it.

Point a hostname at the VM, then set ORIGIN in /opt/airtrail/.env to that URL, for example https://airtrail.example.com if you terminate TLS on a reverse proxy in front. Then restart the service:

sudo systemctl restart airtrail

Update it the same way if the VM's public IP changes, for example after a deallocate and start.

Managing the service

# view recent application logs
sudo docker compose -f /opt/airtrail/docker-compose.yml logs --tail 50 airtrail
# restart the whole stack
sudo systemctl restart airtrail

The upstream release notes and the complete corresponding source for the shipped version are recorded on the VM at /usr/share/doc/airtrail/, alongside the GPL-3.0 licence text.

Support

SecureAnyCloud provides 24/7 support with a 24-hour response SLA. Contact sales@secureanycloud.com for help with this image.

Deploy on Azure

Launch AirTrail 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

Deploy on Azure