A bookmark manager that
lives on your NAS.
HarborMarks saves a link, pulls in its title, description, favicon and preview image, suggests tags from the page's own keywords, and makes the whole collection searchable in Postgres. New links arrive straight from your browser or phone. 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 the moment you paste a link, tags are suggested from the page's own keywords, and assets are 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:, is:favorite and is:broken 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.
Collection Dashboard for cleanup
A dedicated /dashboard page surfaces bookmark debt, broken links, duplicates, unassigned bookmarks, trends, and rediscovery picks — with direct links to jump into each cleanup view.
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.
Clean up in bulk, not one by one
Tick checkboxes to select bookmarks — shift-click grabs a whole range — then favorite, archive, tag, or delete them all at once from the toolbar. Bulk delete still goes to Trash first.
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 on your schedule.
Opt in per user from your profile and pick what lands in the envelope: unread from the last seven days, every unread link, everything you saved this week — or just the broken links — plus the weekday and hour it arrives. 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.
Save it before the tab closes.
The browser extension saves the current tab in one click — title prefilled, tags and a private note optional, duplicates reported instead of erroring, right-click included. On your phone, the iOS Shortcut opens a quick-save page with the same one-tap flow. Both run on per-device API keys from your profile, revocable any time.
harbor…/full-text-search
Settings · Open quick-save page
Collection Dashboard turns backlog into actions.
The dashboard now lives on its own page at/dashboard. It combines a collection score, bookmark debt, broken-link checks, duplicate and unassigned insights, trend snapshots, and rediscovery picks, with direct links into cleanup views so you can fix what matters first.
Collection Dashboard Score
87
ExcellentBookmark Debt
174
Broken Links
21
Duplicates
37
Untagged
142
Dead links find you first.
Turn on health checks and every saved URL is verified once a day — quietly, in the background, never blocking your browsing — with an on-demand Re-run checker action directly in the Collection Dashboard. Rotten links get a red Broken badge and a dimmed preview, answer tois:broken andtag:broken, and can be mailed to you on their own digest scope for a weekend cleanup run.
postgres.org/docs
wiki.example.com/deploy
BrokenClean up in bulk, not one by one.
Tick checkboxes to select bookmarks — shift-click grabs a whole range — then favorite, archive, tag, or delete them all at once from the toolbar pinned above the cards. Bulk delete still lands in Trash first, and tag changes keep your existing casing instead of duplicating it.
github.com/rust-lang
wiki.example.com/backup
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.