Linkyard is a self-hosted semantic bookmarking app. Save URLs through a browser extension or the web UI, then search your collection by meaning — not just keywords. Links are embedded using a local sentence-transformer model (no external API required by default) and stored in Postgres with pgvector.
![]() |
![]() |
![]() |
| Links view | Semantic search | Settings |
Want searchable content from the first launch? Set DEMO_SEED=true in .env before make up and the backend pre-loads ~40 diverse links (programming, ML, design, philosophy, science) on first boot, so semantic search returns meaningful results immediately. Try queries like "functional programming", "how memory works", or "design heuristics". The loader is idempotent — it no-ops once your database has any links — so it's safe to leave on.
Continue with Quick start below to get going.
- Docker and Docker Compose v2
- Chrome or Chromium (for the browser extension)
-
Clone the repository.
git clone <repo-url> linkyard cd linkyard
-
Create your environment file and set a strong admin token.
cp .env.example .env
Open
.envand setADMIN_TOKENto a random secret:openssl rand -hex 32
-
Build the image, start services, and run migrations.
make build && make up && make migrate
-
Confirm the backend is running.
curl http://localhost:8000/healthz
You should get a
200 OKresponse. -
Load the extension (see next section).
- Open Chrome and navigate to
chrome://extensions. - Enable Developer mode (top-right toggle).
- Click Load unpacked and select the
extension/directory in this repo. - Click the Linkyard icon in the toolbar, then open the options page (gear icon).
- Set the backend URL to
http://localhost:8000and save.
The popup will now let you save the current tab to Linkyard with an optional note.
All runtime configuration is set via environment variables in .env. The values below are the ones most likely to need changes.
| Variable | Default | Description |
|---|---|---|
ADMIN_TOKEN |
(empty) | Bearer token for /settings endpoints. Generate with openssl rand -hex 32 before go-live. |
CORS_ORIGIN_REGEX |
chrome-extension://[a-p]{32} |
Regex matched against the Origin header. Pin to your extension's ID before go-live (find it on chrome://extensions after loading unpacked). |
EMBEDDING_PROVIDER |
local |
local (sentence-transformers) or openai. |
EMBEDDING_DIM |
384 |
Must match the model output dimension. local = 384, OpenAI text-embedding-3-small = 1536. |
DEMO_SEED |
false |
When true, the backend pre-loads ~40 example links on first boot if the database is empty. Safe to leave on; the loader no-ops once links exist. |
Before exposing the backend publicly, set
CORS_ORIGIN_REGEXtochrome-extension://<your-exact-extension-id>to prevent other extensions from calling your backend. Your extension ID is shown onchrome://extensionsafter loading unpacked.
| Command | Description |
|---|---|
make build |
Build the backend Docker image. |
make up |
Start all services in detached mode. |
make down |
Stop all services. |
make migrate |
Run Alembic migrations inside the backend container. |
make logs |
Tail backend logs (Ctrl-C to stop). |
On first save, the local embedding model (multi-qa-MiniLM-L6-cos-v1, ~90 MB) downloads automatically — this may take a minute. Subsequent saves are fast.
If you switch EMBEDDING_PROVIDER after saving links, go to the Settings tab in the web UI and click Re-embed all links to rebuild embeddings with the new model.



