A travel itinerary is one of the most sensitive documents you produce all year, because it tells a stranger where you sleep, when your house is empty, which passport you carry, what card you pay with, and who you are traveling with, and yet the default workflow for managing one is to forward all of it, confirmation email by confirmation email, into a hosted service whose business model you have never read, and we got tired of pretending that trade was normal, so we built the thing we actually wanted to use.
That thing is Roamarr, a self-hosted travel organizer written in SvelteKit 2 on top of MongrelDB, and it tracks flights, stays, documents, companions, reminders, expenses, and sharing from a single container that runs on your own hardware, with no hosted account, no sync service in the middle, and no company holding a copy of your movement history for its own reasons.
Why the itinerary is the sensitive part
People tend to think of travel apps as calendar helpers, which is how they get away with the data collection, because a calendar entry feels harmless until you realize that a full itinerary is a structured record of your absence from home, your border crossings, your companions, and your payment methods, all timestamped, all correlated, and all sitting on infrastructure you do not control under terms you did not negotiate.
The hosted incumbents in this space have known this for years, and the product shape reflects it: sign up with an email, forward your confirmations, let the service parse them, and in exchange get a tidy timeline and a mobile app, with the parsing and the retention happening somewhere you cannot inspect and cannot delete from with any confidence. When we looked at what it would take to get the same convenience without the handover, the answer turned out to be “less than we expected,” because the hard part of a travel organizer was never the parsing, it was always the trust, and trust is a deployment decision, not a feature.
Self-hosting stopped being a weird answer to this somewhere in the last few years, partly because running one container on a home server or a five-dollar VPS went from hobbyist ritual to ordinary maintenance, and partly because enough hosted services have leaked, shuttered, or changed their terms that “I keep it on my own box” reads as prudence now instead of paranoia, the same way running your own password manager went from eccentric to recommended.
What Roamarr actually is
Roamarr is a SvelteKit 2 application backed by MongrelDB, packaged as one container, and that packaging choice is load-bearing, because a self-hosted tool that requires a weekend of YAML to stand up is a tool that only its authors will ever run, so the entire deployment is one image with the app, the database, and the schema baked in, and you point it at a volume and you are done.
The data model covers the parts of a trip that actually need tracking: flights with segments and confirmation numbers, stays with check-in and check-out and address details, documents like passports and visas with expiry dates, companions linked to the trips they share, reminders tied to deadlines like check-in windows and document renewals, expenses in multiple currencies, and sharing for the specific case where a travel partner needs the itinerary without needing an account on your server.
Sensitive fields, the passport numbers and card references and document scans, are encrypted at rest rather than stored as plain columns, which matters because the threat model for a self-hosted travel app is not a nation state, it is a stolen backup or a borrowed disk, and encryption at rest with a sensible default means the backup you rsync to a second drive is not a plaintext copy of your travel life sitting in an untarred directory. The key is derived from a passphrase you set on first run and never written into the database volume, because an encryption key stored next to the ciphertext is decoration rather than protection, and the backup only stays safe if the thing that unlocks it lives in your head or your password manager instead of in the same tarball.
The owner authenticates to the instance with that same passphrase, which keeps the account model at exactly one user plus scoped share links and avoids the whole apparatus of OIDC providers and password resets for what is usually a single-person deployment, and sharing deserves a sentence of its own, because the naive design is “invite your companion to your instance,” and the correct design is a scoped, revocable link that exposes one trip and nothing else, since the person who needs to know your flight lands at 18:40 does not need read access to your expense history or your other trips, and least-privilege turns out to be as good a rule for spouses as it is for services.
Why SvelteKit 2 and MongrelDB
The stack was not chosen to be interesting, it was chosen to be boring in the specific ways that make a self-hosted app survive contact with its users, and SvelteKit 2 earns its place because the compiled output is small, the server-side rendering is straightforward, and the framework does not ask you to learn a client-side data-fetching religion before you can render a form, which matters when the whole app is forms and lists and the occasional timeline view.
MongrelDB earns its place for a different reason, and it is the same reason we reach for it everywhere else: it is our own database, we know exactly how it behaves at rest and under backup, the schema enforcement happens in the engine rather than in application code that someone might bypass, and running the travel app’s database on the same engine we ship commercially means every bug we fix for a paying customer is a bug fixed for the app on the shelf in the closet, which is the kind of alignment you do not get from a dependency someone else maintains on their own schedule. The honest counterweight is that betting on your own engine is the opposite of boring in the usual sense, since a single-vendor database rises and falls with the vendor, and we accept that trade knowingly because the vendor is us and the alternative, a rotating cast of third-party storage libraries with their own release cadences, has bitten us more often than our own code ever has.
Because the app, the database, and the schema travel together in one image, upgrades are the part that has to be designed rather than improvised, so a new container version runs its schema migrations against the existing volume on first boot and refuses to start if the data is newer than the code, which is the only sane contract when the database and the application move in lockstep, and it is the reason “just pull the new image” has stayed a safe sentence so far.
The combination also keeps the resource footprint honest, because a single container running SvelteKit’s server via the Node adapter and an embedded MongrelDB instance idles at a fraction of what the average travel SaaS runs per customer, and that is not a brag so much as an observation that the workload, a handful of users reading and writing a few thousand rows a year, was never going to need anything heavier, and sizing the stack to the actual workload instead of the imagined one is most of what “self-hostable” means in practice.
What you give up
The honest tradeoff is that hosted travel apps do two things Roamarr does not do for you: they parse your confirmation emails automatically, and they push flight status changes from a live data feed, and both of those are features that require either a mailbox integration or a paid upstream data source, which are exactly the kinds of dependencies that reintroduce the third party we were trying to remove, so Roamarr takes manual entry with good forms over automatic ingestion with a privacy hole, and treats status alerts as the airline’s job rather than the organizer’s.
This is the same trade the whole self-hosted category makes, and it is worth stating plainly because the pitch for self-hosting sometimes pretends there is no cost, when the real position is that the cost is small and known and paid in minutes of typing, while the cost of the hosted version is diffuse and ongoing and paid in data you cannot take back, and we would rather type our confirmation numbers than wonder which analytics partner is reading them.
The modern equivalent of this whole design is not a new idea at all, it is the same instinct that made people run their own mail servers in 2003, except this time the tooling is good enough that the advice survives contact with normal humans: one container, one volume, one backup job, and the itinerary lives where the traveler lives, which is where it should have been all along.
