New releases
Recipe version compared with the latest known upstream release. A version number, not a reading of what changed.
Linkwarden saves, organizes, and searches bookmarks, preserves local copies of pages, and shares collections. This recipe includes PostgreSQL 16, Meilisearch 1.13.3, a separate origin for preserved HTML, and a web port safely bound to localhost.
Linkwarden is a bookmark manager and web archive for one user, a family, or a small team. It organizes links into collections, adds tags, provides full-text search, and preserves pages and documents for reading after the original disappears. Shared collections, a browser extension, and official Android and iOS clients are available.
This recipe runs pinned Linkwarden v2.16.2 with PostgreSQL 16 and Meilisearch 1.13.3. Preserved content, the database, and the search index each use persistent storage. The web port binds to localhost, internal services are not published, and preserved HTML is served from a separate origin.
The full HTTP smoke test has passed on amd64; backup and restore remain untested
in practice. A headless browser fetches untrusted URLs and can consume substantial
CPU, RAM, and disk. Private-network access and insecure TLS are forced off, but
that does not replace an egress firewall for untrusted users. Preserved HTML is
isolated on a separate origin without shared cookies. Registration ships open so
that the first account can be created, and nothing closes it afterwards: set
NEXT_PUBLIC_DISABLE_REGISTRATION=true and recreate the container as soon as
you have signed up, or anyone who reaches the URL can register.
These are stored results of separate checks, not real-time data. An unknown status means the check has not been performed yet.
Recipe version compared with the latest known upstream release. A version number, not a reading of what changed.
Result of a dedicated Docker image scan, not a Compose configuration check.
A new major version is found by comparing version numbers. A compatibility verdict is entered by hand after reading the release notes and the migration steps.
Upstream Compose changes relative to the verified recipe.
Pinned image tags checked for newer versions.
From observation to action
The recipe already pins the latest known release. The steps below show the evidence retained for that version.
v2.16.2v2.16.2Open this release on GitHub ↗ The link is evidence to review, not proof of compatibility.
The version numbers do not cross a major compatibility boundary. No compatibility verdict has been recorded.
No image-tag change is currently planned.
Manual steps after the pull. Release-specific migration steps still come from the release notes and installation guide.
No version change is currently planned. The recipe includes a backup procedure. Restore has not been tested.
An exact Fully verified check for this target was recorded on 2026-09-03.
The status shows how fully the published recipe meets each level's requirements.
A minimal setup for a local network or access through a VPN.
You still need to add: single server, local access, minimum components.
A public deployment with protected access and backups.
You still need to add: domain, https via reverse proxy, off-server backup.
A setup designed for predictable operations and observability.
You still need to add: external monitoring, cpu and memory limits.
Tailored to your server
Answer five questions and download a ready-to-run Linkwarden bundle.
Screenshots have not been added yet.
Read the full guide first: it explains external access, backups, and data locations.
cp .env.example .envdocker compose up -ddocker compose psUse Ubuntu 22.04+ or Debian 12+ with Docker Engine and Compose v2.24+. Allocate at least 2 CPUs, 2 GB RAM, and 5 GB disk; 4 GB RAM plus dedicated room for web archives is recommended. The official image declares amd64 and arm64.
docker --version
docker compose version
mkdir -p ~/services/linkwarden
cd ~/services/linkwarden
cp .env.example .env
chmod 600 .env
nextauth_secret="$(openssl rand -hex 32)"
postgres_password="$(openssl rand -hex 32)"
meili_key="$(openssl rand -hex 32)"
sed -i "s|^NEXTAUTH_SECRET=.*|NEXTAUTH_SECRET=$nextauth_secret|" .env
sed -i "s|^POSTGRES_PASSWORD=.*|POSTGRES_PASSWORD=$postgres_password|" .env
sed -i "s|^MEILI_MASTER_KEY=.*|MEILI_MASTER_KEY=$meili_key|" .env
unset nextauth_secret postgres_password meili_key
All three secrets are required, must differ, and must remain stable after the first start. A hexadecimal value is safe inside the PostgreSQL URL. Store the secrets in a password manager.
Every .env variable:
LINKWARDEN_PORT is the local web port, default 3000;LINKWARDEN_URL is the exact public application URL without a trailing /;LINKWARDEN_USER_CONTENT_URL is a separate HTTPS origin for preserved HTML, preferably on another registrable domain and without shared cookies;NEXT_PUBLIC_DISABLE_REGISTRATION stays false only while creating the first account and must then become true;NEXTAUTH_SECRET is the persistent NextAuth session and token secret;POSTGRES_PASSWORD is an independent URL-safe PostgreSQL password;MEILI_MASTER_KEY is an independent Meilisearch master key;POSTGRES_DB and POSTGRES_USER are the database and role names, changed only before the first start;LINKWARDEN_TIME_ZONE is an IANA time zone;LINKWARDEN_DATA_VOLUME, LINKWARDEN_DB_VOLUME, and LINKWARDEN_MEILI_VOLUME name archive, PostgreSQL, and search-index volumes;LINKWARDEN_BACKUP_DIR selects the host backup directory.The recipe derives NEXTAUTH_URL as ${LINKWARDEN_URL}/api/v1/auth, while
BASE_URL equals LINKWARDEN_URL. Do not omit /api/v1/auth from the auth URL.
docker compose config
docker compose pull
docker compose up -d --wait --wait-timeout 900
docker compose ps
curl -I http://127.0.0.1:3000/
Open LINKWARDEN_URL, create the first account, immediately set
NEXT_PUBLIC_DISABLE_REGISTRATION=true, and apply it:
docker compose up -d --force-recreate linkwarden
Use a private window to confirm sign-up is unavailable. Do not expose the service publicly before this step.
Keep 127.0.0.1:${LINKWARDEN_PORT}:3000; only an HTTPS proxy on the host can
reach the app. PostgreSQL and Meilisearch have no published ports. Allow only
SSH, HTTP, and HTTPS through the firewall. Create DNS for both the application
and user-content origins before the first start.
Prefer ssh -L 3000:127.0.0.1:3000 user@server and use
LINKWARDEN_URL=http://localhost:3000 for that route. For permanent LAN access,
replace the localhost bind with one specific private IP, use a matching URL, and
restrict the port with a firewall. Do not use 0.0.0.0 without network controls.
Without HTTPS, preserved-content origin isolation is weaker, so this mode is for
a trusted network only.
Set LINKWARDEN_URL=https://links.example.com and a separate
LINKWARDEN_USER_CONTENT_URL=https://saved.example.net, then replace both hosts
in the Caddy, Nginx, or Traefik example. For stronger isolation, use another
registrable domain rather than an application subdomain, and never set shared
parent-domain cookies. The certificate must cover both hosts; replace the Nginx
sample paths with a suitable SAN or separate certificates. The proxy must
support WebSockets and preserve Host and X-Forwarded-Proto.
chmod +x backup.sh restore.sh
./backup.sh
The script stops Linkwarden and Meilisearch, keeps PostgreSQL available for a
native pg_dump, archives /data/data, /meili_data, .env, and Compose, then
restarts the services. A UI JSON export is not a complete backup: it omits
preserved pages, documents, and extracted text. The archive holds secrets and
user content; encrypt it, copy it off the server, and test restores regularly.
Restore irreversibly replaces application data, PostgreSQL, and the Meilisearch
index. Use the same Linkwarden, PostgreSQL, and Meilisearch versions with the
active .env:
./restore.sh ./backups/linkwarden-YYYYMMDDTHHMMSSZ.tar
curl -I http://127.0.0.1:3000/
The script first backs up the state being replaced, recreates all three volumes,
restores the dump, and starts the stack. The archived configuration.env is
retained for comparison only. This procedure has not passed a practical restore
test; test it on a separate server first.
Create a backup, read release notes, and replace the exact
ghcr.io/linkwarden/linkwarden:v2.16.2 tag with a reviewed version. Never use
latest, and do not update PostgreSQL or Meilisearch at the same time:
docker compose pull
docker compose up -d --wait --wait-timeout 900
curl -I http://127.0.0.1:3000/
docker compose logs --tail=200 linkwarden postgres meilisearch
Application database migrations run at startup. A PostgreSQL major upgrade requires a new empty volume and native-dump restore. A Meilisearch major change requires its upgrade guide; retain the old volume until search is verified.
Never run an older Linkwarden over a database after newer migrations. Restore
all previous exact tags and the complete pre-update archive with restore.sh.
After a failed PostgreSQL or Meilisearch change, attach the old image only to its
preserved old volume, or restore a compatible dump/backup into an empty volume.
docker compose down preserves data. After verifying an off-server backup,
remove everything irreversibly:
docker compose down
docker volume rm linkwarden-data linkwarden-postgres linkwarden-meilisearch
rm -rf ~/services/linkwarden
Substitute actual names when volume variables differ.
Sources: self-hosting setup, environment variables, user-content domain, release v2.16.2, and PostgreSQL upgrades.
docker compose ps
docker compose logs --tail=200 linkwarden postgres meilisearch
docker inspect --format '{{json .State.Health}}' "$(docker compose ps -q linkwarden)"
curl -I http://127.0.0.1:3000/
Check the three required distinct secrets, dependency health, free RAM, and disk
space. POSTGRES_PASSWORD must be URL-safe because it appears in DATABASE_URL.
Never delete production volumes while diagnosing a failure.
LINKWARDEN_URL must exactly match the public scheme and host without a trailing
/. The recipe passes NEXTAUTH_URL=${LINKWARDEN_URL}/api/v1/auth and
BASE_URL=${LINKWARDEN_URL}. Recreate Linkwarden after changing it and remove old
cookies. Ensure the proxy forwards the original Host and
X-Forwarded-Proto=https.
Set NEXT_PUBLIC_DISABLE_REGISTRATION=true, run
docker compose up -d --force-recreate linkwarden, and check in a private
window. A simple restart is insufficient when the container was not recreated.
Check LINKWARDEN_USER_CONTENT_URL, DNS, TLS, and the second proxy host. The
origin must differ from the application and must not share its cookies. Never
work around this by enabling private-network access or insecure TLS.
This is expected protection: Compose pins ALLOW_PRIVATE_NETWORK_ACCESS=false
and ALLOW_INSECURE_TLS=false. Do not weaken them on a shared instance. For a
trusted internal resource, prefer a public endpoint with valid HTTPS or a
separate isolated instance with an egress firewall.
docker compose logs --tail=200 meilisearch linkwarden
docker compose exec meilisearch curl --fail http://127.0.0.1:7700/health
Ensure both services have the same MEILI_MASTER_KEY and the volume is not full.
Do not delete the index before a complete backup. If upstream provides a reindex
command for this version, run it only after preserving PostgreSQL and archives.
On the host, run curl -I http://127.0.0.1:3000/. Inspect the upstream, firewall,
and proxy namespace. Inside a container, 127.0.0.1 refers to the proxy itself,
not Linkwarden on the host; use a host gateway or shared private network.
Fossary is an independent informational catalog and is not affiliated with the developers of the listed applications. We collect information from public sources and verify published recipes, but we do not develop, distribute, or control these applications.
A listing or validation status is not a requirement, endorsement, or personal recommendation to use an application. You decide whether it is suitable and use it at your own risk. We do not warrant an application's security, availability, or fitness for purpose and, to the extent permitted by law, are not liable for resulting loss or damage. Review its license, security settings, data processing, and backup procedures before installation.