AdventureLog Travel Tracker and Trip Planner on AWS User Guide
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.serviceandadventurelog.serviceas 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 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 Map view pins every adventure on an interactive world map, so you can see your travels at a glance:

The World Travel view tracks which of the world's countries and regions you have visited, with per-region 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.