Cara Self-Host LinkBreeze sebagai Alternatif Linktree
Jalankan LinkBreeze di VPS dengan Docker Compose dan Caddy. Gunakan tag image yang dipatok, pelacakan klik tanpa cookie, dan satu volume untuk seluruh situs.
Apa itu LinkBreeze
LinkBreeze adalah alternatif Linktree yang di-host sendiri: satu container Docker yang menyediakan halaman publik berisi tautan di bio dan dashboard admin, dengan seluruh state tersimpan dalam satu file SQLite. Perangkat lunak ini berlisensi MIT, ditulis dalam TypeScript menggunakan Next.js, dan dipublikasikan sebagai ghcr.io/manak-hash/linkbreeze. Untuk menjalankannya, Anda memerlukan VPS, domain dengan A record yang mengarah ke VPS tersebut, port 80 dan 443 yang terbuka, serta Docker Engine dengan plugin Compose.
Panduan ini membahas deployment yang benar-benar didukung oleh repository tersebut: Docker Compose di balik reverse proxy yang mengambil sertifikatnya sendiri. Panduan ini juga membahas hal-hal yang dapat gagal, karena tautan di bio adalah URL publik yang diklik orang lain, dan tautan yang rusak dapat membuat Anda kehilangan klik.
Sebelum membahas semua itu, pahami terlebih dahulu bahwa proyek ini masih sangat baru.
Apakah LinkBreeze sudah cukup matang untuk tautan profil publik?
Per Agustus 2026, repositori ini memiliki 178 star, 17 fork, dan satu maintainer. Rilis bertag pertama, v1.0.0, bertanggal 1 Juli 2026. Proyek ini baru berusia beberapa minggu, bukan 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, proyek ini telah merilis 17 rilis bertag dalam 7 minggu kalender. Minggu terakhir pada bagan tersebut masih berlangsung saat panduan ini ditulis dan sudah mencakup 3 dari seluruh rilis itu.
Pahami ini sebagai dua fakta terpisah. Maintainer aktif dan bug diperbaiki dalam hitungan hari. Skema dan nilai default juga masih sering berubah. Karena itu, instance yang Anda deploy lalu tinggalkan tanpa pemeliharaan dapat semakin jauh berbeda dari kode yang sedang dikembangkan.
Lisensi melindungi Anda dari skenario terburuk. MIT, image container, dan file SQLite pada disk Anda sendiri berarti bahwa jika pengembangan berhenti, apa yang sudah Anda miliki akan tetap berjalan. Namun, lisensi tidak melindungi Anda dari aplikasi web yang menghadap publik dan berhenti menerima perbaikan keamanan. Seiring waktu, kondisi tersebut dapat menjadi risiko. Deploy aplikasi ini dengan rencana untuk terus memperbaruinya, dan pastikan prosedur pencadangan di bawah ini berjalan sejak hari pertama.
Sematkan tag image, dan jangan gunakan latest
Alur kerja rilis mendorong tepat dua tag untuk setiap versi: latest dan nomor versi setelah awalan v dihapus. Jadi, tag yang disematkan untuk rilis v1.2.7 adalah ghcr.io/manak-hash/linkbreeze:1.2.7. Menuliskan :v1.2.7 tidak mengunduh apa pun, dan Docker melaporkan manifest unknown karena tag tersebut tidak pernah didorong.
Sematkan tag tersebut karena latest berubah. Dengan frekuensi pada grafik di atas, docker compose pull terhadap latest merupakan upgrade tanpa peninjauan pada halaman yang sedang digunakan audiens Anda. Dengan tag yang disematkan, upgrade terjadi saat Anda mengedit file.
Ada satu hal lagi tentang image tersebut. Alur kerja rilis melakukan build tanpa pengaturan platforms:, sehingga image yang dipublikasikan hanya linux/amd64. Pada host arm64, pull gagal dengan no matching manifest for linux/arm64/v8 in the manifest list entries. Jika Anda menjalankan VPS ARM, bukan x86, lakukan build image pada mesin tersebut:
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 image dalam file compose di bawah ini.
Deploy LinkBreeze di balik Caddy dengan TLS otomatis
Caddy meminta dan memperbarui sertifikat dari Let's Encrypt secara mandiri, sehingga TLS (transport layer security) tidak memerlukan langkah sertifikat terpisah. Seluruh deployment terdiri dari tiga file dalam satu direktori.
Buat secret terlebih dahulu:
mkdir -p ~/linkbreeze && cd ~/linkbreeze
printf 'SECRET_KEY=%s\n' "$(openssl rand -hex 32)" > .env
chmod 600 .envSECRET_KEY menandatangani cookie sesi admin dan menambahkan salt pada hash pengunjung analytics. File compose yang dipublikasikan di repository menetapkan nilai default ${SECRET_KEY:-changeme-in-production}, sehingga instance yang melewati langkah ini akan berjalan dengan session signing key yang ditampilkan secara publik di GitHub. Tetapkan nilainya sebelum start pertama, karena perubahan setelah itu akan mengeluarkan Anda dari sesi dan mereset salt analytics.
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 bersifat opsional, tetapi sebaiknya ditetapkan. Nilai ini memberi tahu aplikasi alamat publik sebenarnya, sehingga request dengan header Host palsu tidak dapat membuat aplikasi menghasilkan link ke domain milik orang lain.
Tulis Caddyfile di sebelahnya, menggunakan domain Anda sendiri:
links.example.com {
encode zstd gzip
reverse_proxy linkbreeze:3000
}Caddy menetapkan X-Forwarded-For dan X-Forwarded-Proto pada request yang diproxy secara default. Analytics bergantung pada kedua header tersebut. Jalankan:
docker compose up -d
docker compose ps
docker compose logs -f caddydocker compose ps seharusnya menampilkan container LinkBreeze dengan status healthy. Image ini menyediakan healthcheck sendiri, yaitu wget --spider -q http://127.0.0.1:3000/api/health, sehingga Anda tidak perlu menambahkannya. Jangan menyalin healthcheck dari contoh Caddy milik repository: healthcheck tersebut memanggil curl, sedangkan image ini dibangun di atas node:22-alpine, yang menyediakan busybox wget dan tidak menyediakan curl. Container tersebut melaporkan unhealthy meskipun tetap dapat menyajikan halaman dengan baik.
Buka https://links.example.com di browser. Kunjungan pertama akan membuka setup wizard di /setup, yang membuat satu akun admin. Setelah itu, dashboard tersedia di /dashboard dan formulir login di /login. Akun tersebut hanya berlaku pada instance ini, dan aplikasi tidak menyediakan hook single sign-on. Jadi, jika Anda ingin dashboard menggunakan login yang sama dengan semua layanan lain yang Anda host, fungsi tersebut harus disediakan oleh forward auth proxy di depannya, seperti Authentik yang di-host sendiri.
Perhatikan hal yang tidak dilakukan file compose: file tersebut tidak pernah memublikasikan port 3000. Hanya Caddy yang listen pada interface publik. Jika sintaks file Compose masih baru bagi Anda, dasar-dasar Docker Compose untuk VPS menjelaskan bagian yang diasumsikan oleh file ini. Jika Anda sudah menjalankan layanan lain di depannya, perbandingan Nginx, Caddy, dan Traefik menjelaskan perubahan yang diperlukan. Repository menyediakan contoh yang dapat langsung digunakan untuk Nginx dengan Certbot, Traefik, dan tunnel Cloudflare.
Lokasi data dan isi yang harus ada dalam backup
DATABASE_PATH menunjuk ke /app/data/linkbreeze.db. Avatar yang diunggah dan thumbnail tautan ditulis di sebelahnya dalam /app/data/uploads. Keduanya berada dalam named volume linkbreeze-data, sehingga unit backup adalah volume, bukan file database secara terpisah. Jika file dipulihkan tanpa direktori uploads, setiap gambar pada halaman akan menghasilkan 404.
Semua data lainnya memang berada dalam satu database tersebut: halaman, tautan, pengaturan, tema, pelanggan email, dan baris analytics.
Buat salinan saat container dihentikan:
docker compose stop linkbreeze
docker compose cp linkbreeze:/app/data ./backup-$(date +%F)
docker compose start linkbreezeHentikan container terlebih dahulu karena menyalin database SQLite saat suatu proses sedang menulis ke dalamnya dapat menangkap transaksi yang belum selesai. Salinan tersebut kemudian dapat terbuka sebagai file yang rusak. Halaman tidak dapat diakses selama proses penyalinan. Pemulihan dilakukan dengan cara sebaliknya:
docker compose stop linkbreeze
docker compose cp ./backup-2026-08-14/. linkbreeze:/app/data
docker compose start linkbreeze
docker compose logs -f linkbreezeDashboard juga menyediakan ekspor JSON yang disajikan dari /api/backup sebagai linkbreeze-backup-YYYY-MM-DD.json. File ini berisi profil, tautan, pengaturan, dan tema yang disimpan. File ini tidak berisi riwayat analytics, pelanggan email, atau gambar yang diunggah. Saat dipulihkan, file ini menghapus baris saat ini dalam keempat tabel tersebut sebelum memasukkan baris dari file. Perlakukan file ini sebagai snapshot konfigurasi untuk memindahkan host atau membatalkan kesalahan pengeditan. Salinan volume adalah backup.
Dua aturan penyimpanan berikut juga berlaku seperti pada penggunaan lain saat Anda menjalankan SQLite di production pada VPS. Simpan database pada disk lokal karena penguncian SQLite tidak andal pada filesystem jaringan, dan Anda baru akan mengetahuinya saat halaman database rusak. Jika Anda mengganti named volume dengan host bind mount, jalankan chown pada direktori host terlebih dahulu: container berjalan sebagai user non-root node, dengan uid 1000 dalam node:22-alpine, dan direktori yang dibuat oleh root tidak dapat ditulisi oleh user tersebut. Akibatnya, aplikasi tidak dapat membuka database dan container berhenti saat startup. Bind mount dibandingkan named volume dalam Compose membahas pertukaran tersebut secara lengkap.
Analitik dan banner persetujuan yang tidak Anda perlukan
Inilah fitur yang membenarkan self-hosting halaman yang dapat Anda peroleh secara gratis di tempat lain.
Analitik ini tidak menggunakan cookie. Tidak ada cookie yang dibuat untuk pengunjung dan tidak ada skrip pihak ketiga yang dimuat pada halaman publik. Pengunjung diidentifikasi menggunakan hash SHA-256 dari alamat IP, string user agent, dan salt, yang dipotong menjadi 16 karakter heksadesimal. Salt itu sendiri merupakan hash dari tanggal UTC saat ini dan SECRET_KEY Anda. Karena itu, nilainya berubah pada tengah malam UTC dan hash dari kemarin tidak dapat dicocokkan dengan hash hari ini. Alamat IP mentah tidak pernah ditulis ke database.
Klik dihitung di server. Setiap tautan http pada halaman publik mengarah ke /go/<id> pada domain Anda sendiri. Endpoint tersebut mencatat klik, lalu mengirim respons redirect 302 ke tujuan sebenarnya. Dengan demikian, penghitungan tetap berfungsi bagi pembaca yang menonaktifkan JavaScript dan bagi pembaca yang menggunakan browser dalam aplikasi yang memblokir permintaan latar belakang. Tampilan halaman dicatat melalui /api/track.
Ada dua pengecualian yang perlu diketahui. Permintaan yang membawa sesi admin valid akan dilewati, sehingga pengeditan halaman Anda sendiri tidak menambah angka. User agent crawler yang dikenal juga akan dilewati.
Tentang persetujuan: tidak ada data yang disimpan pada perangkat pembaca, dan cookie yang disimpan pada perangkat pembaca adalah hal khusus yang memerlukan persetujuan melalui banner cookie. Kewajiban Anda tetap bergantung pada lokasi pembaca, jadi periksa ketentuan yang berlaku. Namun, tidak ada cookie pelacakan di sini yang perlu diungkapkan dan tidak ada pihak ketiga yang menerima data tersebut.
Ada satu hal yang sering mengejutkan: jika Anda merotasi SECRET_KEY, salt harian ikut berubah. Akibatnya, setiap pengunjung yang kembali akan dihitung sebagai pengunjung baru sejak saat itu.
Mengapa kolom negara pada analitik kosong?
Karena tidak ada komponen dalam stack Anda yang menetapkan header negara. LinkBreeze menentukan negara dari header proxy seperti cf-ipcountry dan x-vercel-ip-country. Pada VPS di belakang Caddy atau Nginx milik Anda sendiri, header tersebut tidak ada. Akibatnya, negara dicatat sebagai null dan perinciannya tetap kosong. Container tersebut tidak memiliki database GeoIP.
Ada 2 cara untuk mengisinya. Tempatkan Cloudflare di depan domain agar Cloudflare menambahkan cf-ipcountry ke setiap request yang diproksikan. Atau, tetapkan salah satu header tersebut di reverse proxy Anda sendiri berdasarkan pencarian GeoIP lokal.
Masalah terkait berikutnya lebih serius, jadi periksa juga. Handler click dan view membaca alamat client dari X-Forwarded-For terlebih dahulu, lalu X-Real-IP, dan menggunakan 0.0.0.0 sebagai fallback jika kedua header tersebut tidak ada. Jika port 3000 dipublikasikan langsung ke Internet tanpa proxy di depannya, setiap pengunjung akan menghasilkan hash dengan nilai yang sama. Akibatnya, jumlah unique visitors akan selalu terbaca 1, dan rate limit per-IP sebesar 60 event per menit berlaku untuk seluruh audiens Anda sekaligus. Dengan directive reverse_proxy di atas, Caddy menetapkan header tersebut untuk Anda dan kedua masalah ini teratasi.
Impor dari Linktree dan hal-hal yang tidak ikut terimpor
Wizard migrasi pada dashboard menerima URL profil publik atau file hasil ekspor. Wizard ini mengenali halaman linktr.ee, bento.me, lnk.bio, tap.link, hopp.bio, beacons.ai, solo.to, linkfly, mssg.me, dan LittleLink, serta ekspor HTML dan JSON generik. Untuk URL Linktree atau Bento, wizard membaca JSON __NEXT_DATA__ yang disematkan pada halaman tersebut. Untuk halaman statis, wizard membaca tag anchor.
Data yang ikut terimpor mencakup judul, URL, deskripsi, dan gambar setiap tautan, status tautan sebagai profil sosial, serta nama tampilan, bio, dan avatar Anda. Anda dapat memilih tautan yang ditemukan dan ingin dipertahankan sebelum data apa pun ditulis ke database.
Data yang tidak ikut terimpor mencakup riwayat analitik, tema dan tata letak, pelanggan email, tanggal publikasi terjadwal, serta apa pun yang disimpan platform lama di balik login-nya sendiri. Rencanakan untuk membangun ulang tampilannya secara manual. Riwayat klik lama tetap berada di layanan lama.
Importer mengambil URL dari server Anda, bukan dari browser Anda. Karena itu, importer menolak alamat yang tidak bersifat publik. Private/local URLs are not allowed berarti Anda memberikan alamat di dalam jaringan Anda sendiri. Penolakan ini disengaja. Tanpanya, siapa pun yang memiliki akses ke dashboard dapat menggunakan server Anda untuk memindai mesin yang hanya dapat dijangkau oleh server Anda. Pesan lain yang mungkin Anda lihat adalah Only http and https URLs are allowed, Request timed out, dan Response too large.
Scraping bergantung pada markup milik pihak lain. Jika wizard tidak menemukan apa pun pada halaman yang jelas memiliki tautan, berarti platform tersebut mengubah HTML-nya setelah parser dibuat. Tambahkan tautan secara manual, alih-alih menunggu perbaikan. Jika yang sebenarnya Anda perlukan adalah tautan pendek yang dapat diukur, bukan halaman profil, penyingkat URL yang di-host sendiri seperti Shlink dapat menjalankan tugas tersebut dan berjalan baik pada server yang sama.
Memperbarui deployment yang dipatok
# edit the image tag in docker-compose.yml, then
docker compose pull
docker compose up -d
docker compose logs -f linkbreezeMigrasi skema berjalan otomatis saat container dimulai. Tidak ada cara yang terdokumentasi untuk menjalankannya secara mundur, jadi buat salinan volume terlebih dahulu. Upgrade yang tidak dapat dibatalkan hanya aman jika Anda dapat memulihkan kondisi sebelumnya.
Dashboard menampilkan banner jika tersedia release yang lebih baru. Dashboard memeriksa hal ini dengan mengambil file versi kecil dari repositori GitHub project tersebut sekali setiap 24 jam, dan tidak mengirimkan informasi apa pun tentang instance Anda. Baca catatan release sebelum mengubah tag, karena pada tahap pengembangan project ini, versi minor dapat mengubah default yang Anda andalkan.
Mode kegagalan dan string yang akan Anda lihat
manifest unknown saat melakukan pull. Tag tersebut ditulis sebagai :v1.2.7. Tag registry tidak memiliki v, jadi gunakan :1.2.7.
no matching manifest for linux/arm64/v8 in the manifest list entries. Image yang dipublikasikan hanya mendukung amd64. Build image tersebut pada host ARM dari source yang memiliki tag tersebut.
Container melaporkan unhealthy saat halaman dapat dimuat dengan baik. Healthcheck dalam file compose Anda memanggil curl, yang tidak terdapat dalam image. Hapus healthcheck tersebut dan biarkan healthcheck wget bawaan image berjalan.
Caddy menampilkan error sertifikat atau tidak menampilkan apa pun. Periksa docker compose logs caddy. Penyebab yang umum adalah A record belum mengarah ke VPS ini, atau port 80 tertutup pada firewall. Kondisi tersebut memblokir HTTP challenge ACME (automatic certificate management environment) yang digunakan Caddy untuk membuktikan bahwa Caddy mengendalikan domain tersebut.
Unique visitors tetap bernilai 1. Tidak ada proxy yang menetapkan X-Forwarded-For, sehingga hash setiap pengunjung identik.
Container langsung berhenti setelah startup, padahal kemarin masih berfungsi. Jika Anda berpindah dari named volume ke host bind mount, direktori data dimiliki oleh root, sedangkan aplikasi berjalan sebagai uid 1000. Akibatnya, aplikasi tidak dapat membuka file database. sudo chown -R 1000:1000 direktori pada host.
Permintaan tracking dijawab dengan HTTP 429. Throttle per-IP pada /api/track dan /go/<id> telah tercapai. Pengunjung tetap dialihkan ke tujuan, tetapi klik tersebut tidak dihitung.
FAQ
Apakah LinkBreeze siap digunakan untuk tautan publik di bio?
Proyek ini masih baru. Per Agustus 2026, repositorinya memiliki 178 star, 17 fork, dan satu maintainer, sementara rilis pertamanya bertanggal 1 Juli 2026. Rilis baru hadir rata-rata lebih dari dua kali seminggu, sehingga bug diperbaiki dengan cepat, tetapi perilakunya juga dapat berubah dengan cepat. Lisensi MIT dan file SQLite lokal membuat halaman tetap dapat digunakan meskipun pengembangan berhenti. Namun, aplikasi web publik tanpa perbaikan keamanan akan menjadi risiko, jadi anggap ini sebagai perangkat lunak yang harus terus diperbarui, bukan diinstal sekali lalu dibiarkan.
Tag image LinkBreeze mana yang sebaiknya saya jalankan?
Gunakan tag versi, misalnya ghcr.io/manak-hash/linkbreeze:1.2.7, dan ubah secara sengaja. Alur kerja rilis hanya mengunggah latest dan nomor versi tanpa awalan, sehingga :v1.2.7 dengan v tidak ada dan Docker mengembalikan manifest unknown. Image ini hanya dibuat untuk linux/amd64. Jadi, pada VPS arm64, Anda harus meng-clone tag tersebut dan melakukan build secara lokal.
Mengapa rincian negara tetap kosong dalam analitik LinkBreeze?
LinkBreeze membaca negara pengunjung dari header proxy seperti cf-ipcountry atau x-vercel-ip-country. LinkBreeze tidak memiliki database GeoIP sendiri. VPS yang berada di belakang Caddy atau Nginx milik Anda sendiri tidak menetapkan header tersebut, sehingga negara disimpan sebagai null. Tempatkan Cloudflare di depan domain, atau minta reverse proxy menetapkan salah satu header tersebut berdasarkan pencarian GeoIP lokal.
Apa saja yang harus dicadangkan, dan bagaimana cara memulihkannya?
Cadangkan seluruh volume linkbreeze-data, bukan hanya file database. /app/data/linkbreeze.db menyimpan semua tautan, halaman, pengaturan, subscriber, dan baris analitik. /app/data/uploads menyimpan gambar avatar dan thumbnail yang dirujuk halaman. Hentikan container, jalankan docker compose cp linkbreeze:/app/data ./backup-$(date +%F), lalu jalankan kembali container tersebut. Untuk memulihkan data, salin kembali direktori ke dalam container yang telah dihentikan, lalu jalankan container. Ekspor JSON dari dashboard merupakan snapshot konfigurasi profil, tautan, pengaturan, dan tema. File tersebut tidak berisi data analitik maupun gambar.
Apakah mengimpor dari Linktree juga membawa analitik dan tema saya?
Tidak. Wizard migrasi membaca judul tautan, URL, deskripsi, dan gambar dari profil publik lama Anda, serta nama tampilan, bio, dan avatar Anda. Riwayat analitik, tema, subscriber email, dan tanggal publikasi terjadwal tidak ikut dipindahkan. Bangun kembali tampilan tersebut di editor tema setelah impor, dan perkirakan riwayat klik Anda tetap berada di platform lama.