Cara Self-host LinkBreeze dengan Docker Compose
Ketahui cara memasang LinkBreeze pada VPS menggunakan Docker Compose dan Caddy. Panduan ini merangkumi penggunaan tag imej tetap, penjejakan klik tanpa kuki dan pengurusan volum.
Apakah itu LinkBreeze
LinkBreeze ialah alternatif Linktree yang dihoskan sendiri: satu bekas Docker yang menyediakan halaman pautan-dalam-bio awam dan papan pemuka pentadbir, dengan setiap data disimpan dalam satu fail SQLite. Ia dilesenkan di bawah MIT, ditulis dalam TypeScript menggunakan Next.js, dan diterbitkan sebagai ghcr.io/manak-hash/linkbreeze. Untuk menjalankannya, anda memerlukan VPS, domain dengan rekod A yang menghala ke VPS tersebut, port 80 dan 443 yang dibuka, serta Docker Engine dengan pemalam Compose.
Panduan ini merangkumi penggunaan yang disokong oleh repositori tersebut: Docker Compose di sebalik reverse proxy yang mengambil sijilnya sendiri. Ia juga merangkumi perkara yang boleh menyebabkan kegagalan, kerana pautan dalam bio ialah URL awam yang diklik oleh orang lain, dan pautan yang rosak akan menyebabkan anda kehilangan klik tersebut.
Sebelum memulakan, fahami bahawa projek ini masih baharu.
Adakah LinkBreeze cukup matang untuk pautan profil awam?
Sehingga Ogos 2026, repositori ini mempunyai 178 bintang, 17 fork dan seorang penyelenggara tunggal. Keluaran bertanda pertama, v1.0.0, bertarikh 1 Julai 2026. Ini adalah projek yang berusia beberapa minggu, bukannya beberapa tahun.
The data behind this chart
[
{
"week": "2026-06-29",
"releases": 3,
"cumulative": 3
},
{
"week": "2026-07-06",
"releases": 3,
"cumulative": 6
},
{
"week": "2026-07-13",
"releases": 1,
"cumulative": 7
},
{
"week": "2026-07-20",
"releases": 2,
"cumulative": 9
},
{
"week": "2026-07-27",
"releases": 3,
"cumulative": 12
},
{
"week": "2026-08-03",
"releases": 2,
"cumulative": 14
},
{
"week": "2026-08-10",
"releases": 3,
"cumulative": 17
}
]Sejak v1.0.0, projek ini telah mengeluarkan 17 keluaran bertanda sepanjang 7 minggu kalendar. Minggu terakhir dalam carta tersebut masih berjalan semasa panduan ini ditulis dan sudah pun mempunyai 3 keluaran.
Baca perkara itu sebagai dua fakta yang berasingan. Penyelenggara aktif dan pepijat dibaiki dalam masa beberapa hari. Skema dan tetapan lalai juga masih berubah, jadi instans yang anda pasang dan biarkan begitu sahaja akan jauh terpesong daripada kod yang sedang ditulis.
Lesen tersebut melindungi anda daripada senario terburuk. MIT berserta imej kontena dan fail SQLite pada cakera anda sendiri bermakna jika pembangunan dihentikan, apa yang anda miliki akan terus berjalan. Apa yang tidak dilindungi ialah aplikasi web yang menghadap awam yang berhenti menerima kemas kini keselamatan, yang akan menjadi liabiliti dari masa ke masa. Pasang ini sebagai sesuatu yang akan anda kemas kini secara berterusan, dan pastikan rutin sandaran di bawah berfungsi sejak hari pertama.
Sematkan tag imej, dan jangan jalankan latest
Alur kerja keluaran menolak tepat dua tag bagi setiap versi: latest, dan nombor versi dengan v di hadapan dibuang. Oleh itu, tag yang disemat untuk keluaran v1.2.7 ialah ghcr.io/manak-hash/linkbreeze:1.2.7. Menulis :v1.2.7 tidak menarik apa-apa dan Docker melaporkan manifest unknown, kerana tag tersebut tidak pernah ditolak (pushed).
Sematkan tag tersebut kerana latest sentiasa berubah. Mengikut rentak dalam carta di atas, docker compose pull terhadap latest merupakan naik taraf tanpa semakan bagi halaman yang sedang digunakan oleh audiens anda. Dengan tag yang disemat, naik taraf hanya berlaku apabila anda menyunting fail tersebut.
Satu lagi perkara tentang imej ini. Alur kerja keluaran dibina tanpa tetapan platforms:, jadi imej yang diterbitkan adalah linux/amd64 sahaja. Pada hos arm64, proses menarik (pull) gagal dengan no matching manifest for linux/arm64/v8 in the manifest list entries. Jika anda menjalankan VPS ARM dan bukannya x86, bina imej tersebut pada mesin itu sendiri:
git clone --branch v1.2.7 --depth 1 https://github.com/Manak-hash/LinkBreeze.git
cd LinkBreeze
docker build -t linkbreeze:1.2.7 .Kemudian gunakan linkbreeze:1.2.7 sebagai nama imej dalam fail compose di bawah.
Menggunakan LinkBreeze di sebalik Caddy dengan TLS automatik
Caddy meminta dan memperbaharui sijil daripada Let's Encrypt secara automatik, jadi TLS (transport layer security) tidak memerlukan langkah pengurusan sijil yang berasingan. Keseluruhan proses deployment hanya melibatkan tiga fail dalam satu direktori.
Jana rahsia (secret) terlebih dahulu:
mkdir -p ~/linkbreeze && cd ~/linkbreeze
printf 'SECRET_KEY=%s\n' "$(openssl rand -hex 32)" > .env
chmod 600 .envSECRET_KEY menandatangani kuki sesi admin dan memberikan salt kepada hash pelawat analitik. Fail compose yang diterbitkan dalam repositori menetapkan nilai lalai kepada ${SECRET_KEY:-changeme-in-production}, jadi jika anda melangkau langkah ini, instans anda akan berjalan dengan kunci penandatanganan sesi yang dipaparkan secara awam di GitHub. Tetapkan nilai ini sebelum permulaan pertama, kerana menukarnya kemudian akan menyebabkan anda log keluar dan menetapkan semula salt analitik.
Tulis docker-compose.yml:
services:
linkbreeze:
image: ghcr.io/manak-hash/linkbreeze:1.2.7
restart: unless-stopped
volumes:
- linkbreeze-data:/app/data
environment:
- DATABASE_PATH=/app/data/linkbreeze.db
- SECRET_KEY=${SECRET_KEY}
- BASE_URL=https://links.example.com
networks:
- linkbreeze-net
caddy:
image: caddy:2-alpine
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy-data:/data
- caddy-config:/config
networks:
- linkbreeze-net
networks:
linkbreeze-net:
volumes:
linkbreeze-data:
caddy-data:
caddy-config:BASE_URL adalah pilihan tetapi disyorkan untuk ditetapkan: ia memberitahu aplikasi alamat awam sebenar, supaya permintaan yang tiba dengan header Host palsu tidak boleh menyebabkan aplikasi menjana pautan ke domain orang lain.
Tulis Caddyfile bersebelahan dengannya, menggunakan domain anda sendiri:
links.example.com {
encode zstd gzip
reverse_proxy linkbreeze:3000
}Caddy menetapkan X-Forwarded-For dan X-Forwarded-Proto pada permintaan yang diproksi secara lalai, yang mana analitik bergantung kepadanya. Jalankan aplikasi:
docker compose up -d
docker compose ps
docker compose logs -f caddydocker compose ps sepatutnya menunjukkan kontena LinkBreeze sebagai healthy. Imej ini mempunyai healthcheck sendiri, wget --spider -q http://127.0.0.1:3000/api/health, jadi anda tidak perlu menambahnya. Jangan salin healthcheck daripada contoh Caddy dalam repositori tersebut: ia memanggil curl, manakala imej ini dibina berasaskan node:22-alpine, yang mengandungi busybox wget dan tiada curl. Kontena tersebut akan melaporkan unhealthy walaupun ia berfungsi dengan sempurna untuk menghidangkan halaman.
Buka https://links.example.com dalam pelayar web. Lawatan pertama akan membawa anda ke wizard tetapan di /setup, yang mencipta akaun admin tunggal. Selepas itu, papan pemuka (dashboard) berada di /dashboard dan borang log masuk di /login. Akaun tersebut adalah tempatan bagi instans ini dan tiada hook single sign-on dalam aplikasi, jadi jika anda mahu papan pemuka menggunakan log masuk yang sama dengan servis lain yang anda hoskan, ia perlu dilakukan melalui forward auth proxy di hadapannya seperti Authentik yang dihoskan sendiri.
Perhatikan apa yang tidak dilakukan oleh fail compose tersebut: ia tidak pernah menerbitkan port 3000. Hanya Caddy yang mendengar pada antaramuka awam. Jika sintaks fail Compose adalah perkara baharu bagi anda, asas Docker Compose untuk VPS merangkumi bahagian yang diandaikan oleh fail ini, dan jika anda sudah menjalankan servis lain di hadapan, perbandingan Nginx, Caddy dan Traefik menjelaskan perubahan yang diperlukan. Repositori ini menyediakan contoh yang berfungsi untuk Nginx dengan Certbot, Traefik dan Cloudflare tunnel.
Lokasi data anda dan kandungan sandaran yang diperlukan
DATABASE_PATH menghala ke /app/data/linkbreeze.db. Avatar yang dimuat naik dan imej kecil pautan ditulis di sebelahnya dalam /app/data/uploads. Kedua-duanya berada dalam named volume linkbreeze-data, jadi unit sandaran adalah volume tersebut, bukan sekadar fail pangkalan data itu sendiri. Jika anda memulihkan fail tanpa direktori muat naik, setiap imej pada halaman akan memaparkan ralat 404.
Segala data lain sebenarnya terkandung dalam pangkalan data tersebut: halaman, pautan, tetapan, tema, pelanggan e-mel dan baris analitik.
Lakukan salinan semasa container dihentikan:
docker compose stop linkbreeze
docker compose cp linkbreeze:/app/data ./backup-$(date +%F)
docker compose start linkbreezeHentikan dahulu kerana menyalin pangkalan data SQLite semasa proses sedang menulis kepadanya boleh menangkap transaksi yang belum selesai, dan salinan tersebut kemudiannya akan dibuka sebagai fail yang rosak. Halaman akan berada di luar talian semasa salinan dijalankan. Pemulihan adalah langkah yang sama secara terbalik:
docker compose stop linkbreeze
docker compose cp ./backup-2026-08-14/. linkbreeze:/app/data
docker compose start linkbreeze
docker compose logs -f linkbreezePapan pemuka juga menawarkan eksport JSON, yang dihidangkan daripada /api/backup sebagai linkbreeze-backup-YYYY-MM-DD.json. Ia membawa profil, pautan, tetapan dan tema yang disimpan. Ia tidak membawa sejarah analitik, pelanggan e-mel atau imej yang dimuat naik, dan memulihkannya akan memadamkan baris semasa dalam empat jadual tersebut sebelum memasukkan data daripada fail. Anggap ia sebagai syot kilat konfigurasi untuk menukar hos atau membatalkan kesilapan penyuntingan. Salinan volume adalah sandaran sebenar.
Dua peraturan storan terpakai di sini sama seperti di tempat lain semasa anda menjalankan SQLite dalam pengeluaran pada VPS. Simpan pangkalan data pada cakera tempatan, kerana penguncian SQLite tidak boleh dipercayai pada sistem fail rangkaian dan halaman yang rosak adalah cara anda menyedarinya. Dan jika anda menukar named volume kepada host bind mount, lakukan chown pada direktori hos terlebih dahulu: container berjalan sebagai pengguna bukan root node, uid 1000 dalam node:22-alpine, dan direktori yang dicipta oleh root tidak boleh ditulis olehnya, jadi aplikasi tidak dapat membuka pangkalan data dan container akan keluar semasa permulaan. Bind mounts berbanding named volumes dalam Compose membincangkan pertukaran tersebut dengan lengkap.
Analitis dan sepanduk persetujuan yang tidak anda perlukan
Ini adalah ciri yang mewajarkan tindakan self-hosting bagi halaman yang boleh anda peroleh secara percuma di tempat lain.
Analitis ini tidak menggunakan kuki. Tiada kuki ditetapkan untuk pelawat dan tiada skrip pihak ketiga dimuatkan pada halaman awam. Pelawat dikenal pasti melalui hash SHA-256 bagi alamat IP, rentetan user agent dan salt, yang dipotong kepada 16 aksara heksadesimal. Salt itu sendiri merupakan hash bagi tarikh UTC semasa dan SECRET_KEY anda, jadi ia berubah pada tengah malam UTC dan hash semalam tidak boleh dipadankan dengan hash hari ini. Alamat IP mentah tidak pernah ditulis ke dalam pangkalan data.
Klik dikira pada pelayan. Setiap pautan http pada halaman awam menghala ke /go/<id> pada domain anda sendiri, yang merekodkan klik tersebut dan kemudian menjawab dengan redirect 302 ke destinasi sebenar. Oleh itu, pengiraan berfungsi untuk pembaca yang menyahdayakan JavaScript, dan di dalam pelayar dalam aplikasi (in-app browsers) yang menyekat permintaan latar belakang. Paparan halaman direkodkan melalui /api/track.
Dua pengecualian perlu diketahui. Permintaan yang membawa sesi admin yang sah akan dilangkau, jadi menyunting halaman anda sendiri tidak akan meningkatkan angka tersebut. User agent crawler yang dikenali juga dilangkau.
Mengenai persetujuan: tiada apa-apa yang disimpan pada peranti pembaca, dan kuki yang disimpan pada peranti pembaca adalah perkara khusus yang memerlukan kebenaran daripada sepanduk kuki. Kewajipan anda masih bergantung pada tempat tinggal pembaca anda, jadi semak perkara tersebut, tetapi tiada kuki penjejakan di sini untuk didedahkan dan tiada pihak ketiga yang menerima data tersebut.
Satu amaran yang mengejutkan orang ramai: putarkan SECRET_KEY dan salt harian akan berubah bersamanya, jadi setiap pelawat yang kembali akan dikira sebagai pelawat baharu bermula dari saat itu.
Mengapa lajur negara analitik kosong?
Ini kerana tiada komponen dalam tindanan (stack) anda yang menetapkan pengepala (header) negara. LinkBreeze menyelesaikan maklumat negara daripada pengepala proksi seperti cf-ipcountry dan x-vercel-ip-country. Pada VPS di belakang Caddy atau Nginx anda sendiri, tiada satu pun pengepala tersebut wujud, jadi negara direkodkan sebagai null dan perinciannya kekal kosong. Tiada pangkalan data GeoIP di dalam kontena tersebut.
Terdapat dua cara untuk mengisinya. Letakkan Cloudflare di hadapan domain, yang akan menambah cf-ipcountry pada setiap permintaan yang diproksikannya. Atau, tetapkan salah satu pengepala tersebut dalam proksi terbalik (reverse proxy) anda sendiri daripada carian GeoIP setempat.
Perangkap yang berkaitan adalah lebih buruk, jadi sila semaknya. Pengendali klik dan paparan membaca alamat klien daripada X-Forwarded-For terlebih dahulu, kemudian X-Real-IP, dan menggunakan 0.0.0.0 sebagai sandaran apabila kedua-dua pengepala tersebut tiada. Jika anda menerbitkan port 3000 terus ke internet tanpa proksi di hadapan, setiap pelawat akan di-hash kepada nilai yang sama. Ini bermakna pelawat unik akan sentiasa dibaca sebagai 1, dan had kadar (rate limit) per-IP sebanyak 60 peristiwa seminit akan dikenakan kepada seluruh audiens anda serentak. Di belakang arahan reverse_proxy di atas, Caddy akan menetapkan pengepala tersebut untuk anda dan kedua-dua masalah itu akan selesai.
Import daripada Linktree, dan perkara yang tidak dipindahkan
Wizard migrasi dalam papan pemuka menerima URL profil awam atau fail yang dieksport. Ia mengenali halaman linktr.ee, bento.me, lnk.bio, tap.link, hopp.bio, beacons.ai, solo.to, linkfly, mssg.me dan LittleLink, serta eksport HTML dan JSON generik. Bagi URL Linktree atau Bento, ia membaca __NEXT_DATA__ JSON yang dibenamkan oleh halaman tersebut. Bagi halaman statik, ia membaca tag anchor.
Perkara yang dipindahkan ialah tajuk, URL, deskripsi dan imej bagi setiap pautan, sama ada pautan tersebut merupakan profil sosial, serta nama paparan, bio dan avatar anda. Anda memilih pautan yang ditemui untuk dikekalkan sebelum sebarang data ditulis ke dalam pangkalan data.
Perkara yang tidak dipindahkan ialah sejarah analitik, tema dan susun atur, pelanggan e-mel, tarikh penerbitan berjadual, dan apa-apa sahaja yang disimpan oleh platform lama di sebalik log masuknya sendiri. Rancang untuk membina semula rupa bentuk secara manual, dan terima hakikat bahawa sejarah klik lama kekal pada perkhidmatan lama tersebut.
Pengimport mengambil URL daripada pelayan anda dan bukannya daripada pelayar anda, jadi ia menolak alamat yang tidak bersifat awam. Private/local URLs are not allowed bermaksud anda memberikan alamat di dalam rangkaian anda sendiri, dan penolakan ini adalah sengaja: tanpanya, sesiapa yang mempunyai akses papan pemuka boleh menggunakan pelayan anda untuk meninjau mesin yang hanya boleh dicapai oleh pelayan anda. Mesej lain yang mungkin anda lihat ialah Only http and https URLs are allowed, Request timed out dan Response too large.
Scraping bergantung pada markup pihak lain. Jika wizard tidak menemui apa-apa pada halaman yang jelas mempunyai pautan, platform tersebut telah menukar HTML mereka sejak parser ditulis. Tambah pautan secara manual dan jangan menunggu pembaikan. Jika apa yang anda sebenarnya mahukan ialah pautan pendek yang boleh diukur dan bukannya halaman profil, pemendek URL yang dihoskan sendiri seperti Shlink melakukan tugas tersebut dan berjalan dengan lancar pada mesin yang sama.
Mengemas kini deployment yang dipinkan
# edit the image tag in docker-compose.yml, then
docker compose pull
docker compose up -d
docker compose logs -f linkbreezeMigrasi skema dijalankan secara automatik apabila container bermula. Tiada cara yang didokumentasikan untuk menjalankan migrasi secara terbalik, jadi buat salinan volume terlebih dahulu. Naik taraf yang tidak boleh diterbalikkan hanya selamat apabila anda boleh memulihkan keadaan asal sebelum naik taraf tersebut.
Dashboard akan memaparkan sepanduk apabila keluaran (release) yang lebih baharu tersedia. Ia menyemak dengan mengambil fail versi kecil daripada repositori GitHub projek tersebut setiap 24 jam, dan ia tidak menghantar sebarang maklumat mengenai instance anda. Baca nota keluaran sebelum anda menukar tag, kerana pada peringkat projek ini, versi minor boleh mengubah tetapan lalai yang anda gunakan.
Mod kegagalan dan rentetan yang akan anda lihat
manifest unknown semasa menarik (pulling). Tag tersebut ditulis sebagai :v1.2.7. Tag registry tidak membawa v, jadi gunakan :1.2.7.
no matching manifest for linux/arm64/v8 in the manifest list entries. Imej yang diterbitkan hanya untuk amd64. Bina imej tersebut pada hos ARM daripada sumber yang telah ditag.
Kontena melaporkan unhealthy sedangkan halaman dimuatkan dengan baik. Healthcheck dalam fail compose anda memanggil curl, yang tidak terkandung dalam imej tersebut. Padamkannya dan biarkan healthcheck wget milik imej itu sendiri berjalan.
Caddy memberikan ralat sijil, atau tidak memberikan apa-apa. Semak docker compose logs caddy. Punca biasa ialah rekod A yang belum menghala ke VPS ini, atau port 80 ditutup pada firewall, yang menyekat cabaran HTTP ACME (automatic certificate management environment) yang digunakan oleh Caddy untuk membuktikan ia mengawal domain tersebut.
Pelawat unik tersekat pada 1. Tiada proksi yang menetapkan X-Forwarded-For, jadi setiap pelawat mempunyai hash yang sama.
Kontena keluar sejurus selepas dimulakan, sedangkan ia berfungsi semalam. Jika anda beralih daripada named volume kepada host bind mount, direktori data dimiliki oleh root dan aplikasi berjalan sebagai uid 1000, jadi ia tidak boleh membuka fail pangkalan data. sudo chown -R 1000:1000 direktori hos tersebut.
Permintaan penjejakan dijawab dengan HTTP 429. Had pendikit (throttle) per-IP pada /api/track dan /go/<id> telah dicapai. Pelawat masih dihalakan ke destinasi mereka, klik tersebut cuma tidak dikira.
FAQ
Adakah LinkBreeze sedia untuk pautan bio awam?
Ini adalah projek yang masih baharu. Setakat Ogos 2026, repositori ini mempunyai 178 bintang, 17 fork dan seorang penyelenggara, dengan keluaran pertama bertarikh 1 Julai 2026. Keluaran perisian berlaku lebih daripada dua kali seminggu secara purata, jadi pepijat dibaiki dengan cepat dan tingkah laku perisian juga berubah dengan pantas. Lesen MIT dan fail SQLite tempatan bermakna anda masih mempunyai halaman yang berfungsi walaupun pembangunan dihentikan, namun aplikasi web awam tanpa kemas kini keselamatan akan menjadi liabiliti. Oleh itu, anggap ini sebagai perisian yang perlu anda kemas kini secara berterusan dan bukannya perisian yang dipasang sekali sahaja.
Tag imej LinkBreeze yang manakah perlu saya jalankan?
Jalankan tag versi, contohnya ghcr.io/manak-hash/linkbreeze:1.2.7, dan tukarkannya secara sengaja. Aliran kerja keluaran hanya menolak latest dan nombor versi asas, jadi :v1.2.7 dengan v tidak wujud dan Docker akan memberikan respons manifest unknown. Imej ini dibina untuk linux/amd64 sahaja, jadi pada VPS arm64 anda perlu mengklon tag tersebut dan membina imej secara tempatan.
Mengapakah pecahan negara kekal kosong dalam analitik LinkBreeze?
LinkBreeze membaca negara pelawat daripada header proksi seperti cf-ipcountry atau x-vercel-ip-country, dan ia tidak membawa pangkalan data GeoIP sendiri. VPS di belakang Caddy atau Nginx anda tidak menetapkan mana-mana header tersebut, jadi negara disimpan sebagai null. Letakkan Cloudflare di hadapan domain anda, atau minta reverse proxy anda menetapkan salah satu header tersebut daripada carian GeoIP tempatan.
Apakah yang perlu saya sandarkan, dan bagaimana cara untuk memulihkannya?
Sandarkan keseluruhan volum linkbreeze-data, bukan sekadar fail pangkalan data. /app/data/linkbreeze.db menyimpan setiap pautan, halaman, tetapan, pelanggan dan baris analitik, manakala /app/data/uploads menyimpan imej avatar dan imej kecil yang dirujuk oleh halaman tersebut. Hentikan kontena, jalankan docker compose cp linkbreeze:/app/data ./backup-$(date +%F), kemudian mulakan semula kontena tersebut. Pulihkan dengan menyalin direktori kembali ke dalam kontena yang telah dihentikan dan mulakannya semula. Eksport JSON daripada papan pemuka hanyalah syot kilat konfigurasi profil, pautan, tetapan dan tema, serta tidak mengandungi data analitik mahupun imej.
Adakah mengimport daripada Linktree membawa sekali analitik dan tema saya?
Tidak. Wizard migrasi membaca tajuk pautan, URL, deskripsi dan imej daripada profil awam lama anda, berserta nama paparan, bio dan avatar anda. Sejarah analitik, tema, pelanggan e-mel dan tarikh penerbitan berjadual akan kekal di platform lama. Bina semula rupa halaman dalam editor tema selepas import, dan jangkakan sejarah klik anda kekal di platform lama tersebut.