Files
homelab-configs/AdventureLog-Setup-Summary.md
T

5.0 KiB

AdventureLog Setup Summary

What It Is

AdventureLog is a self-hosted travel logging app with a SvelteKit frontend and Django/PostGIS backend. Live at: https://go.jgitta.com


Infrastructure

Component Detail
Host siklos (192.168.88.27)
Portainer Stack #47 — adventurelog (endpoint 3)
Frontend container adventurelog-frontend — port 8015 (SvelteKit SSR)
Backend container adventurelog-backend — port 8016 (Django/nginx → gunicorn :8000)
Database container adventurelog-db (PostGIS 16-3.5)
Data volumes /srv/docker/adventurelog/postgres_data/ and /srv/docker/adventurelog/media/
Reverse proxy Caddy VM (192.168.88.110) — go.jgitta.com block in /etc/caddy/sites/infrastructure.caddy

Caddy Routing (Corrected 2026-07-02 — now matches official docs)

AdventureLog authenticates through the SvelteKit frontend: on login the frontend keeps the Django session server-side and does NOT hand the browser a Django sessionid cookie (it even clears csrftoken in the browser). Therefore all /api/* and /auth/* traffic must go through the frontend, which proxies to Django server-to-server with the correct session + CSRF token. Only static-type paths go straight to Django.

This is the official AdventureLog Caddy design (https://adventurelog.app/docs/install/caddy.html), adapted for our remote Caddy VM (proxying to LAN ports 8015/8016) plus our standard security headers:

go.jgitta.com {
    import headers_base
    encode zstd gzip
    header {
        X-Frame-Options "SAMEORIGIN"
        Permissions-Policy "geolocation=(self), microphone=(), camera=()"
    }

    # Backend (Django): media, admin, static files, legacy account pages
    @backend path /media* /admin* /static* /accounts*
    handle @backend {
        reverse_proxy http://192.168.88.27:8016 { import proxy_timeouts }
    }

    # Everything else (pages, /api/*, /auth/*) -> SvelteKit frontend,
    # which proxies API/auth to Django server-side with session + CSRF.
    handle {
        reverse_proxy http://192.168.88.27:8015 { import proxy_timeouts }
    }
}

geolocation=(self) (inline, not the web_secure snippet) is required so the "Use My Location" button works.

History — why the old split routing was removed

Earlier the config split /api/* by HTTP method: GET/HEAD/OPTIONS went directly to Django and only writes went through the frontend. That was a workaround for a geocode 401 seen during initial setup. After a frontend container auto-update (:latest) on 2026-07-02, the frontend stopped exposing a Django session to the browser, so the "GET direct to Django" path could no longer authenticate — every authenticated read (including geocoding / map search) returned 401 "Authentication credentials were not provided." Reverting to the official all-/api-through- frontend routing fixed it. Lesson: don't hand-split /api; let the frontend proxy it.


Portainer Environment Variables

All env vars are stored in Portainer's environment section (not hardcoded in compose). Key ones:

Variable Value
PUBLIC_SERVER_URL http://server:8000
ORIGIN https://go.jgitta.com
PUBLIC_URL https://go.jgitta.com
CSRF_TRUSTED_ORIGINS https://go.jgitta.com
FRONTEND_URL https://go.jgitta.com
FRONTEND_PORT 8015
BACKEND_PORT 8016
BODY_SIZE_LIMIT Infinity
POSTGRES_DB adventurelog
POSTGRES_USER adventure
DEBUG False
DISABLE_REGISTRATION False

Secrets (DB password, SECRET_KEY, admin credentials) are in Portainer only — not in this file. Note: DJANGO_ADMIN_PASSWORD is only the bootstrap password used at first container start; the live admin password has since been changed in-app.

Optional/recommended (not currently set): ENABLE_RATE_LIMITS=True (docs recommend for production).


Bugs Fixed During Setup

  1. CSRF errors on loginCSRF_TRUSTED_ORIGINS must match the public URL exactly. Set in env.
  2. GET /api/ returning DRF HTML instead of JSON* — old Caddy did a broken URI rewrite; removed.
  3. Geocode search 401 — see "Caddy Routing" history above. Final fix: route all /api/* through the SvelteKit frontend (official design), not directly to Django.
  4. Category creation 500 — AdventureLog returns 500 (not 400) on duplicate category slug (unhandled IntegrityError). Workaround: delete the duplicate via Django shell.
  5. "Use My Location" blocked — set Permissions-Policy: geolocation=(self) on the site block.

Admin Access


Known Limitations / Not Configured

  • Images run on :latest with Watchtower auto-update — an update on 2026-07-02 changed frontend auth behavior and broke the old routing. Consider pinning image versions.
  • No Google Maps API key — geocoding uses OpenStreetMap/Nominatim (works fine).
  • No Strava integration.
  • B2/Kopia backup not configured for this stack.