A bookmark manager that
lives on your NAS.
HarborMarks saves a link, pulls in its title, description, favicon and preview image, and makes the whole collection searchable in Postgres. One docker compose up -d and it runs on hardware you own — your NAS, a mini PC, or the laptop already on your desk.
- Astro + React 19
- PostgreSQL 17
- Drizzle ORM
- Docker Compose
Deploy an Astro project with a server adapter to render routes per request.
tsvector, tsquery, GIN indexes and ts_rank for ordering matches by relevance.
Declaring stored generated columns and keeping them in sync with migrations.
Uploading a docker-compose.yml to DSM and keeping its volumes on a shared folder.
Automatic HTTPS for a container on your LAN in two lines of Caddyfile.
The CSS-first configuration model and the new @theme directive.
Two containers. Your hardware. Done.
The published image ships the app; the compose file brings its Postgres alongside it. Schema migrations run before the server accepts its first request, so upgrading is a drop-in image swap.
Versioned SQL migrations at startup. Tracked in schema_migrations; a failed migration stops the container instead of serving a half-migrated database.
Healthchecks for your reverse proxy. An unauthenticated GET /api/healthz liveness probe that never touches the database.
Behind a tunnel or proxy? Supported. List your public hostname in HARBOR_ALLOWED_DOMAINS rather than turning the CSRF origin check off.
Your data stays in your volumes. db_data for Postgres, uploads_data for avatars and cached previews. Back them up and you have backed up everything.
ghcr.io/aperezm85/harbormarks:latestlinux/amd64Built for collections that outgrew the browser bar.
Every link you save gets enriched, indexed and organised — and everything you can do to one bookmark, you can undo.
Paste a URL, get a record
Title, description, favicon and preview image are extracted on save, then proxied and cached locally so your library never hotlinks someone else's server.
Search that ranks, not just filters
Postgres full-text search over a generated search_vector with a GIN index and relevance ranking — plus tag:, site: and is:favorite operators.
Tags you can reorganise later
Autocomplete while you save, live counts in the sidebar, and a tag screen that renames a tag everywhere, merges one into another, or deletes it across the whole collection.
Views for how you actually browse
Recent, Unread, Unorganised, Favorites, Trash — and Most visited, weighted by recency so a page you hammered two years ago stops crowding out this month's reading.
Nothing is lost by accident
Delete is soft: an undo toast appears immediately, a Trash view keeps the rest, and permanent deletion is a separate, deliberate action.
No accidental duplicates
URLs are canonicalised on write — host, trailing slash, tracking parameters — and saving one you already have offers open, merge tags, or save anyway.
Skim the previews, or scan a hundred rows.
The view selector switches between preview cards, a compact row with the thumbnail, and a single-line list. Your choice sticks across sessions.
Deploy an Astro project with a server adapter to render routes per request.
Documentation for tsvector, tsquery and ranking.
Declaring stored generated columns in a schema.
The links you meant to read, mailed to you on Sunday.
Opt in per user from your profile and pick what lands in the envelope: unread from the last seven days, every unread link, or everything you saved this week. Clicking a title marks it as reading and records the visit — the same as opening the card — then takes you straight to the page, no login needed.
Deploy an Astro project with a server adapter to render routes per request.
Documentation for tsvector, tsquery and ranking.
Automatic HTTPS in two lines of Caddyfile.
More than one person, properly separated.
- Isolation
- Every list, tag, favourite and visit count is scoped to the user who made it.
- Roles
- An admin screen to create users, assign roles and switch accounts off.
- Signup
- Closed by default on the deploy compose; open it with
HARBOR_ALLOW_SIGNUPfor a private instance. - Accounts
- Password reset and email verification over your own SMTP, with a server-log fallback.
The boring parts are already done.
- CSRF
- Origin checking on by default, evaluated per request, with proxy-aware
Securecookies. - SSRF
- Outbound fetches for metadata, previews and favicons are guarded before they leave the container.
- Rate limits
- Login, registration and recovery endpoints are throttled; expired sessions and tokens clean themselves up.
- Sessions
- Database-backed session tokens with role-based guards on every admin route.
Bring your links home.
Clone the repo or pull the image, set a bootstrap admin password, and you have a bookmark manager that answers only to you.