Applications AWS

AdventureLog Travel Tracker and Trip Planner on AWS User Guide

| Product: AdventureLog

Overview

AdventureLog is a self-hosted travel tracker and trip planner. Log the countries and regions you have visited, plan upcoming trips, pin individual adventures on an interactive map, organise them into collections and store your travel photos, all from a single web application that stays entirely on your own instance. Under the hood AdventureLog is a Django and GeoDjango REST backend backed by PostgreSQL with the PostGIS spatial extension, paired with a SvelteKit web frontend.

This cloudimg image runs AdventureLog as the three official upstream containers, each pinned by immutable digest, orchestrated by docker compose under a single systemd service. The SvelteKit frontend is published on host port 80, the Django backend (REST API, uploaded media and the Django admin) on host port 8016, and PostgreSQL with PostGIS is reachable only inside the private container network - it is never published to a host port. The Docker data root, holding the pre-pulled digest-pinned images, the travel database and your uploaded photos, lives on a dedicated data volume mounted at /var/lib/docker, so your travel data survives OS-disk changes and is resizable independently.

Security is enforced from first boot. AdventureLog upstream ships build-time defaults for its Django secret key, PostgreSQL password and administrator password; this image never ships those to a customer. Before the stack the customer sees ever starts, a unique Django secret key, PostgreSQL password and administrator password are generated for the individual instance on a fresh database, and every customer-facing URL is re-pointed at the instance's own reachable address. Backed by 24/7 cloudimg support.

What is included:

  • AdventureLog v0.12.1, the GPL-3.0 licensed travel tracker and trip planner, shipped as the three official upstream containers pinned by immutable digest
  • The SvelteKit frontend on host port 80 and the Django and GeoDjango backend on host port 8016
  • A bundled PostgreSQL 16 with the PostGIS spatial extension, reachable only inside a private container network
  • Docker Engine and the docker-compose plugin from the official Docker APT repository
  • A unique Django secret key, PostgreSQL password and administrator password generated on each instance's first boot, on a fresh database
  • The Docker data root, travel database and uploaded media on a dedicated data volume at /var/lib/docker
  • docker.service, adventurelog-firstboot.service and adventurelog.service as systemd units, enabled and active
  • 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. Security group inbound: allow 22/tcp from your management network for SSH, and 80/tcp and 8016/tcp for the web UI and backend API from wherever your users browse.

Step 1 - Launch the AMI

Subscribe to the listing in AWS Marketplace, then launch an instance from the AMI into your VPC with the security group described above. Choose the m5.large instance type and attach your EC2 key pair. The image keeps the three pre-pulled container images on the dedicated data volume, so the AdventureLog stack comes up within a few minutes of first boot with no image re-pull while it rotates its secrets and initialises a fresh database.

Step 2 - Connect to your instance

Connect over SSH as the default login user for the operating system variant you launched. The login user differs per OS, so use the row for your variant:

OS variant SSH login user Example
Ubuntu 24.04 ubuntu ssh -i your-key.pem ubuntu@<instance-public-ip>
ssh -i your-key.pem ubuntu@<public-ip>

Step 3 - Read your unique credentials

On first boot the instance generates a unique administrator password, seeds a fresh administrator on a clean database, and writes the credentials along with the resolved web URL to a root-only file. Read it with sudo:

sudo cat /root/adventurelog-credentials.txt

You will see output of this shape (the URL and password are unique to your instance):

# AdventureLog self-host — generated on first boot by adventurelog-firstboot.service
# These credentials are unique to this instance. Store them somewhere safe.

adventurelog.url=http://<instance-public-ip>
adventurelog.admin.username=admin
adventurelog.admin.email=admin@cloudimg.local
DJANGO_ADMIN_PASSWORD=<unique-per-instance-password>

# The Django admin backend is at http://<instance-public-ip>:8016/admin/ .
# Browse to http://<instance-public-ip>/ and sign in as 'admin' with the password above.

There is no default or shared login anywhere in the image; every secret is unique to this instance.

Step 4 - Verify the security model from the command line

Confirm the three services are active and the stack is running the digest-pinned images:

systemctl is-active docker adventurelog adventurelog-firstboot
sudo docker compose -f /var/lib/adventurelog/docker-compose.yml ps --format "table {{.Service}}\t{{.Status}}\t{{.Ports}}"
active
active
active
SERVICE   STATUS         PORTS
server    Up 6 minutes   8000/tcp, 0.0.0.0:8016->80/tcp, [::]:8016->80/tcp
db        Up 6 minutes   5432/tcp
web       Up 6 minutes   0.0.0.0:80->3000/tcp, [::]:80->3000/tcp

Note that the db (PostgreSQL with PostGIS) container exposes 5432/tcp on the container only - there is no host mapping, so the database is reachable exclusively inside the private container network. Confirm that from the host's listening sockets: only SSH, the frontend and the backend are bound, never PostgreSQL:

sudo ss -tlnp | grep -E ":80 |:8016 |:5432 " | awk '{print $1, $4}'
LISTEN 0.0.0.0:80
LISTEN 0.0.0.0:8016

Port 5432 returns nothing on the host: PostgreSQL is never exposed. Confirm the frontend and backend both answer locally:

curl -s -o /dev/null -w 'frontend %{http_code}\n' http://127.0.0.1/
curl -s -o /dev/null -w 'backend  %{http_code}\n' http://127.0.0.1:8016/api/
frontend 200
backend  200

Step 5 - Sign in to the web UI

Open the web UI in a browser at http://<instance-public-ip>/ and sign in as admin with the password from /root/adventurelog-credentials.txt. You land on your dashboard, which summarises how many adventures you have logged and how much of the world you have visited:

The AdventureLog dashboard welcoming the administrator with visited-countries, regions and cities statistics and recent adventures

The Locations view lists every adventure you have logged, with a filter and sort sidebar, each card showing its place, rating and visited or planned status:

The AdventureLog Locations view listing logged adventures with a filter and sort sidebar

The Map view pins every adventure on an interactive world map, so you can see your travels at a glance:

The AdventureLog interactive map pinning logged adventures across Europe

The World Travel view tracks which of the world's countries and regions you have visited, with per-region progress:

The AdventureLog World Travel country list tracking visited countries and travel progress

Step 6 - Log an adventure through the REST API

AdventureLog exposes a REST API on the backend port (8016) that the web UI uses, and that you can drive directly. The example below authenticates as your per-instance administrator, creates a collection, reads it back to confirm it persisted to PostgreSQL, and then shows that an unauthenticated create is rejected. Read the admin password into a shell variable first so it never appears on screen:

PW=$(sudo grep '^DJANGO_ADMIN_PASSWORD=' /root/adventurelog-credentials.txt | cut -d= -f2-)
B=http://127.0.0.1:8016
J=$(mktemp)
# establish a CSRF-protected session as the admin
curl -s -c $J -b $J -o /dev/null $B/accounts/login/
C=$(awk '$6=="csrftoken"{print $7}' $J | tail -1)
curl -s -c $J -b $J -o /dev/null -w 'login %{http_code}\n' \
  -H "Referer: $B/accounts/login/" -H "Origin: $B" \
  --data-urlencode "login=admin" --data-urlencode "password=$PW" \
  --data-urlencode "csrfmiddlewaretoken=$C" $B/accounts/login/
# create a collection, then read it back
C=$(awk '$6=="csrftoken"{print $7}' $J | tail -1)
curl -s -c $J -b $J -H 'Content-Type: application/json' -H "X-CSRFToken: $C" \
  -H "Origin: $B" -H "Referer: $B/" -X POST $B/api/collections/ \
  -d '{"name":"My first trip"}' -w '\ncreate %{http_code}\n'
# an unauthenticated create is rejected (no anonymous data)
curl -s -o /dev/null -w 'unauth %{http_code}\n' -H 'Content-Type: application/json' \
  -X POST $B/api/collections/ -d '{"name":"nope"}'
rm -f $J

The authenticated login returns 302 and the create returns 201; the unauthenticated create returns a non-2xx status, proving the API assigns your identity on write and never persists anonymous objects.

Step 7 - Point the instance at your own domain

First boot points every URL at the instance's public IP so login and CSRF work out of the box. If you reach the instance by a DNS name or a different address (for example behind an Application Load Balancer), edit the four URL settings in the environment file and restart the service:

sudo sed -i \
  -e 's|^ORIGIN=.*|ORIGIN=https://<your-domain>|' \
  -e 's|^FRONTEND_URL=.*|FRONTEND_URL=https://<your-domain>|' \
  -e 's|^PUBLIC_URL=.*|PUBLIC_URL=https://<your-domain>:8016|' \
  -e 's|^CSRF_TRUSTED_ORIGINS=.*|CSRF_TRUSTED_ORIGINS=https://<your-domain>,https://<your-domain>:8016|' \
  /var/lib/adventurelog/.env
sudo systemctl restart adventurelog.service

For production, terminate TLS at an Application Load Balancer or your own reverse proxy in front of ports 80 and 8016, using a certificate for your domain.

Data volume, backups and updates

The Docker data root at /var/lib/docker is a dedicated EBS data volume. It holds the three digest-pinned images, the PostgreSQL and PostGIS travel database (the postgres_data volume) and your uploaded photos (the adventurelog_media volume). Because it is a separate disk you can grow it independently of the OS disk, and you can back it up with EBS snapshots or AWS Backup. cloudimg support can help you plan backups and schedule restores.

Open source and source availability

AdventureLog is licensed under the GNU General Public License version 3. This image ships the official upstream container images unmodified; the complete corresponding source for the version shipped here (v0.12.1) is available from the upstream project at github.com/seanmorley15/AdventureLog. cloudimg's charges are for the packaging, hardening and support services described here, not for the software licence. cloudimg is not affiliated with, endorsed by, or sponsored by the AdventureLog project; the AdventureLog name and logo are used nominatively to identify the software.

Support

cloudimg provides 24/7 technical support for this product by email and live chat at support@cloudimg.co.uk. We help with deployment, re-pointing the instance at your own domain or DNS name, reverse-proxy TLS termination, and backup planning for your travel database and uploaded media.