Atlas is a self-hosted travel app for one user or a small household. It manages a trip end-to-end: the planning, the documents you receive by email, the day-of-departure checklist, and the trip itself. Boarding passes, hotel confirmations, and PKPasses are parsed locally by a small LLM, so flights, hotels, and transit show up as segments without manual entry. Trips also include activities, undated wishlist items, freeform notes, and two map views — a per-trip map with flight arcs and a global map of every country you've been to. All extraction, geocoding, and tile serving happens on your own hardware. Built for homelab self-hosting on the same machine you already run Nextcloud, Immich, or Plex on.
- Trips & itineraries — flights, hotels, activities, transit, and notes in one timeline.
- Document extraction — local PDF → OCR → LLM pipeline. Files stay on your disk.
- Maps — global countries-visited choropleth plus per-trip flight arcs and pins. Self-hosted basemap.
- Passwordless auth — passkeys via PocketID (OIDC).
- Wishlist mode — undated activities pinned to a trip, promoted to the itinerary when scheduled.
- Multi-leg flights — one document, one PNR, many segments. Edits propagate in a single transaction.
- Backups — daily Postgres + document snapshots, ready for offsite rsync.
- Notifications — push reminders via self-hosted ntfy (planned).
- Installable (PWA) — add to your home screen for a full-screen, app-like launch; pages you've opened stay viewable offline.
- Create a trip with a title and dates.
- Upload everything from your inbox — boarding passes, hotel confirmations, train tickets, PKPasses.
- Run extraction. Ollama parses each file locally; segments auto-generate and link back.
- Itinerary usually complete in minutes.
- Add activities or wishlist items for anything not yet booked.
Atlas is a Progressive Web App, so it installs to your phone's home screen and launches full-screen without browser chrome.
- Android (Chrome): open Atlas, then "Install app" from the menu (or the install prompt).
- iOS (Safari): open Atlas, tap Share → Add to Home Screen. iOS has no install prompt, so this step is manual.
Once installed, a small service worker caches what you open, so an itinerary you loaded before takeoff stays viewable offline. Offline is read-only (adding or editing a segment needs the server), and the trip-map basemap isn't available offline (tiles are too large to cache) — the trip map shows an offline notice, while flight arcs, pins, and the countries-visited map still render.
The service worker is hand-rolled rather than generated by a PWA build plugin:
those plugins are webpack-based, and Atlas builds with Turbopack (Next 16's
default). Once a Turbopack-native precaching plugin is stable (most likely a
stable @serwist/turbopack, or first-party Next support), we'll migrate to it
for full offline precaching — see ADR-0017.
Global map of everywhere you've been.
Tabbed itinerary with a country filter for multi-country trips.
Uploaded files with extraction status and parsed fields.
Click a pin to zoom in on a city and see its details.
| Layer | Choice |
|---|---|
| Framework | Next.js 16 (App Router, RSC) |
| Language | TypeScript (strict) |
| Styling | Tailwind CSS + shadcn/ui |
| Database | PostgreSQL 18 + Drizzle ORM |
| Storage | Local filesystem (swappable interface) |
| Maps | MapLibre GL JS + Protomaps PMTiles |
| Auth | Auth.js + PocketID (OIDC, passkeys) |
| Extraction | pdf-parse → Tesseract/Paddle → Ollama |
| Geocoding | Nominatim with a DB-backed cache |
| Notifications | ntfy (planned) |
| Testing | Vitest + Playwright |
| Containers | Docker + Docker Compose |
The full rationale and architectural rules live in CLAUDE.md.
Decisions of consequence are recorded as ADRs in docs/adr/.
Each release publishes two images from one version: the app at
ghcr.io/sebbogit/atlas and the pg-boss worker at
ghcr.io/sebbogit/atlas:<tag>-worker.
Create a .env (start from .env.example) with at least a
POSTGRES_PASSWORD, your PocketID OIDC settings, and ATLAS_IMAGE_TAG (pin a
release, e.g. 1.4.2).
The repo ships a
docker-compose.yml plus a hardened
docker-compose.prod.yml overlay (resource limits,
runtime confinement, scheduled backups). For a production deployment, use those
and follow docs/DEPLOYMENT.md (PocketID, basemap,
Ollama, reverse proxy, backups):
docker compose -f docker-compose.yml -f docker-compose.prod.yml --profile backup up -dOpen http://localhost:3000. The map stays blank until you fetch the basemap, and extraction needs an Ollama host — both are covered in the deploy guide.
Requires: Node.js 24, pnpm 9.15+, Docker with Compose v2.
git clone https://github.com/SebboGit/atlas.git
cd atlas
pnpm install
cp .env.example .env
# Edit .env
pnpm dev:up # one-shot: postgres → migrate → seed → next devOpen http://localhost:3000. For the development workflow, conventions, and
gates, see docs/DEVELOPMENT.md.
Everything stateful lives under ./data/:
data/
├── documents/ User-uploaded files (immutable originals)
├── tiles/ Protomaps PMTiles basemap (regenerable, not backed up)
└── backups/
├── db/ ZSTD-compressed Postgres dumps
└── documents/ Timestamped document snapshots
| Doc | What's in it |
|---|---|
docs/DEPLOYMENT.md |
End-to-end deployment: PocketID, basemap, Ollama, reverse proxy, backups |
docs/DEVELOPMENT.md |
Dev setup, quality gates, conventions, git workflow |
docs/ARCHITECTURE.md |
High-level system design |
docs/DOMAIN_MODEL.md |
Entities, relationships, invariants |
docs/THREAT_MODEL.md |
Assets, trust boundaries, and the threats Atlas defends against |
docs/adr/ |
Architecture Decision Records |
CLAUDE.md |
Canonical rules and conventions (the source of truth) |
Atlas is stable. Trips, segments, documents and extraction, both map surfaces, search, stats, and the wishlist are all implemented, and releases follow Semantic Versioning. Push notifications (ntfy) are the next planned addition.
- Use, modify, fork, redistribute — fine, for any noncommercial purpose.
- Charities, schools, universities, public bodies — fine.
- Selling it, paid hosting, shipping it as part of a paid product — not fine.
Unclear use case? Open a discussion before acting.




