راهاندازی کوتاهکننده URL خودمیزبان با Shlink و Docker
با Shlink 5.1 و Docker Compose روی VPS یک کوتاهکننده URL شخصی بسازید؛ از DNS و Postgres تا کلید API، web client، کد QR و آمار کلیک را پیکربندی کنید.
چیزی که میسازید
یک کوتاهکننده URL خودمیزبان، سرور کوچکی است که یک پیوند طولانی را به پیوند کوتاهی تحت مالکیت شما تبدیل میکند و هر کلیک روی آن را میشمارد. Shlink گزینه مناسبی است: متنباز است، بهصورت یک Docker image عرضه میشود و کل کار را با یک container بهعلاوه یک پایگاه داده انجام میدهد. این راهنما آن را روی یک VPS، پشت یک دامنه کوتاه واقعی، همراه با HTTPS، کلید API، کدهای QR و آمار کلیک راهاندازی میکند.
دو بخش باعث میشوند این سامانه مانند یک کوتاهکننده تجاری عمل کند. سرور API به redirectها پاسخ میدهد و دادهها را نگهداری میکند. web client یک برنامه ایستای جداگانه است که از مرورگر شما با آن API ارتباط برقرار میکند. میتوانید هر دو را اجرا کنید، یا فقط API را اجرا کنید و آن را از خط فرمان کنترل کنید.
شماره نسخههای این راهنما مربوط به وضعیت فعلی در July 2026 هستند: Shlink 5.1 و shlink-web-client 4.8.
ابتدا یک دامنه کوتاه را به سرور هدایت کنید
دامنه همان محصول است. s.example.com/abc123 پیوندی است که افراد میبینند؛ بنابراین نامی کوتاه انتخاب کنید و پیش از نصب هر چیزی آن را مشخص کنید. Shlink دامنه را همراه هر URL کوتاه ذخیره میکند و تغییر آن در آینده باعث میشود همه پیوندهایی که قبلاً در اختیار دیگران گذاشتهاید، از کار بیفتند.
یک رکورد DNS از نوع A برای دامنه کوتاه ایجاد کنید و آن را به نشانی عمومی IPv4 مربوط به VPS خود هدایت کنید. اگر سرور IPv6 دارد، یک رکورد AAAA نیز اضافه کنید. سپس پیش از ادامه کار، بررسی کنید که دامنه resolve میشود.
dig +short s.example.com Aخروجی باید نشانی سرور شما باشد. اگر خروجی خالی است، رکورد هنوز propagate نشده است. در این حالت همه مراحل بعدی بهشکلی نامشخص شکست میخورند، زیرا گواهی TLS (transport layer security) را نمیتوان برای نامی صادر کرد که resolve نمیشود.
فایل compose
Shlink به یک پایگاه داده نیاز دارد. SQLite برای آزمایش مناسب است، اما برای هر چیزی که قصد دارید حفظ کنید، Postgres انتخاب درست است؛ زیرا ردیفهای بازدید افزایش مییابند و Postgres شاخصها و نوشتنهای همزمان را بهتر مدیریت میکند. این محتوا را در /opt/shlink/compose.yaml قرار دهید.
services:
shlink:
image: shlinkio/shlink:stable
restart: unless-stopped
ports:
- "127.0.0.1:8080:8080"
environment:
DEFAULT_DOMAIN: s.example.com
IS_HTTPS_ENABLED: "true"
DB_DRIVER: postgres
DB_HOST: database
DB_NAME: shlink
DB_USER: shlink
DB_PASSWORD: ${DB_PASSWORD}
depends_on:
- database
database:
image: postgres:17-alpine
restart: unless-stopped
environment:
POSTGRES_DB: shlink
POSTGRES_USER: shlink
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- shlink_db:/var/lib/postgresql/data
web-client:
image: shlinkio/shlink-web-client:stable
restart: unless-stopped
ports:
- "127.0.0.1:8081:8080"
volumes:
shlink_db:هر دو پورت منتشرشده به 127.0.0.1 متصل میشوند؛ بنابراین تا زمانی که reverse proxy در بخش بعدی راهاندازی نشده باشد، چیزی از اینترنت قابل دسترسی نیست. Docker قوانین انتقال خود را پیش از فایروال میزبان اعمال میکند؛ بنابراین یک خط ساده 8080:8080 برنامه را حتی روی سیستمی که فایروالش بسته به نظر میرسد، در معرض دسترسی قرار میدهد. اتصال به نشانی loopback از این وضعیت جلوگیری میکند. همین الگو برای هر برنامهای که به این روش اجرا میکنید کاربرد دارد و در راهنمای Docker Compose روی VPS با جزئیات بیشتری توضیح داده شده است.
گذرواژه پایگاه داده از یک فایل .env در کنار فایل compose خوانده میشود؛ بنابراین هرگز در YAML قرار نمیگیرد.
sudo mkdir -p /opt/shlink
printf 'DB_PASSWORD=%s\n' "$(openssl rand -base64 24)" | sudo tee /opt/shlink/.env
sudo chmod 600 /opt/shlink/.envآن را اجرا کنید و بالا آمدن API را monitor کنید.
cd /opt/shlink
sudo docker compose up -d
sudo docker compose logs -f shlinkاولین اجرا migrationهای پایگاه داده را انجام میدهد؛ بنابراین از اجراهای بعدی طولانیتر است. پس از پایدار شدن سرویس، بررسی کنید که سرویس بهصورت محلی پاسخ میدهد.
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080/rest/healthکد 200 نشان میدهد API فعال است و اتصال پایگاه داده کار میکند. کد 500 در اینجا تقریباً همیشه به پایگاه داده مربوط است: مقدار DB_PASSWORD در .env با مقداری که Postgres هنگام ایجاد شدن با آن پیکربندی شده است مطابقت ندارد؛ زیرا image مربوط به Postgres مقدار POSTGRES_PASSWORD را فقط هنگام راهاندازی یک data directory خالی میخواند. تغییر گذرواژه بعداً اثری ندارد، مگر اینکه volume را حذف کنید و سرویس را دوباره راهاندازی کنید.
پایاندادن به HTTPS در مقابل آن
Shlink روی پورت 8080، HTTP ساده ارائه میکند. TLS باید در یک reverse proxy مدیریت شود و تنها تنظیم مهم، انتقال نام میزبان اصلی است. Shlink با خواندن هدر Host تعیین میکند یک کد کوتاه به کدام دامنه تعلق دارد. بنابراین، proxyای که این هدر را بازنویسی کند، برای پیوندهای موجود پاسخهای 404 ایجاد میکند و آمار بازدید را به دامنه نادرست نسبت میدهد.
server {
server_name s.example.com;
listen 80;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}سپس گواهی را صادر کنید. راهنمای کامل، از جمله timer مربوط به تمدید، در راهنمای Certbot برای nginx در Ubuntu 24.04 آمده است.
sudo certbot --nginx -d s.example.comIS_HTTPS_ENABLED: "true" در فایل compose باعث میشود Shlink در URLهای کوتاهی که برمیگرداند، https:// را چاپ کند. این گزینه بهتنهایی TLS را فعال نمیکند. آن را پشت یک proxy با HTTPS، یعنی false، قرار دهید؛ در غیر این صورت هر پیوندی که API برمیگرداند، یک پیوند http:// است که سپس redirect میشود. این کار یک رفتوبرگشت اضافی ایجاد میکند و در web client نادرست به نظر میرسد.
ایجاد کلید API
هیچ چیزی بدون کلید نمیتواند با API ارتباط برقرار کند. کلید را از طریق CLI داخل container ایجاد کنید.
sudo docker compose exec shlink shlink api-key:generate --name "web client"این فرمان کلید را فقط یکبار نمایش میدهد. اکنون آن را کپی کنید، زیرا کلید بهصورت hashشده ذخیره میشود و دیگر قابل نمایش نیست. shlink api-key:list نامها و وضعیت فعالبودن هر کلید را نمایش میدهد، اما خود کلید را هرگز نشان نمیدهد. برای لغو یک کلید، shlink api-key:disable و نام آن را وارد کنید.
هر فراخوانی REST کلید را در یک header از نوع X-Api-Key ارسال میکند.
curl -H "X-Api-Key: YOUR_KEY" https://s.example.com/rest/v3/short-urlsیک شیء JSON دارای کلید shortUrls نشان میدهد که کلید کار میکند. وجود INVALID_API_KEY در 401 به این معناست که کلید نادرست، غیرفعال یا منقضی شده است.
ایجاد پیوندهای کوتاه از خط فرمان
CLI سریعترین روش برای ایجاد پیوند است و برای استفاده در اسکریپتها نیز مناسب است.
sudo docker compose exec shlink shlink short-url:create https://example.com/a/very/long/path
sudo docker compose exec shlink shlink short-url:create https://example.com/docs --custom-slug docs --tag reference--custom-slug بهجای کد تولیدشده، پیوندی خوانا ایجاد میکند. slugها در هر دامنه یکتا هستند؛ بنابراین اگر slugی قبلاً استفاده شده باشد، تلاش دوم با خطا مواجه میشود و پیوند اول بیصدا بازنویسی نمیشود. --tag را میتوان تکرار کرد. tagها برای گروهبندی پیوندهایی هستند که بعداً به آمار تجمیعی آنها نیاز خواهید داشت.
ابتدا موارد موجود را فهرست کنید، سپس ترافیک یکی از پیوندها را بررسی کنید.
sudo docker compose exec shlink shlink short-url:list
sudo docker compose exec shlink shlink short-url:visits docsshort-url:visits برای هر کلیک یک ردیف شامل تاریخ، ارجاعدهنده و user agent چاپ میکند. ستونهای کشور و شهر خالی میمانند، مگر اینکه متغیر محیطی GEOLITE_LICENSE_KEY را تنظیم کنید. این متغیر یک کلید رایگان MaxMind است که Shlink از آن برای دانلود پایگاه داده GeoLite2 استفاده میکند. بدون این کلید، بازدیدها همچنان ثبت میشوند، اما مکان آنها مشخص نمیشود.
کلاینت وب و کدهای QR
کلاینت وب اکنون در 127.0.0.1:8081 قرار دارد و به یک ورودی پراکسی مستقل نیاز دارد؛ اگر ترجیح میدهید آن را منتشر نکنید، از یک تونل SSH استفاده کنید. کلاینت در نخستین بارگذاری، URL سرور و یک کلید API را درخواست میکند. https://s.example.com و کلیدی را که ایجاد کردهاید وارد کنید. کلاینت هر دو مورد را در فضای ذخیرهسازی مرورگر نگه میدارد و مستقیماً API شما را فراخوانی میکند؛ بنابراین هیچ دادهای از طریق شخص ثالث عبور نمیکند.
کدهای QR به هیچ پیکربندی نیاز ندارند. /qr-code را به هر URL کوتاه اضافه کنید تا API تصویر را برگرداند.
https://s.example.com/docs/qr-code?size=500&format=svg&margin=20size عرض تصویر برحسب پیکسل است و مقادیری از 50 تا 1000 را میپذیرد؛ مقدار پیشفرض آن 300 است. format مقدار png یا svg است. margin فضای خالی پیرامون کد برحسب پیکسل است و اندازه تصویر نهایی برابر با اندازه کد بهعلاوه دو برابر حاشیه خواهد بود. برای کدی که حتی هنگام چاپ در اندازه کوچک یا پوشیدهشدن بخشی از آن نیز قابل اسکن باشد، errorCorrection=Q را اضافه کنید.
آن را در حال اجرا نگه دارید
یک سرویس کوتاهکننده ممکن است بدون اعلام خطا از کار بیفتد. پیوندها دیگر به مقصد هدایت نمیشوند و کسی به شما اطلاع نمیدهد، زیرا فردی که روی پیوند کلیک کرده است تصور میکند پیوند از کار افتاده است. بررسی زمان فعالیت را روی یک URL کوتاه واقعی تنظیم کنید، نه روی صفحه اصلی، و برای هر وضعیتی که redirect نیست هشدار تنظیم کنید. یک نمونه Uptime Kuma خودمیزبان این کار را بهخوبی انجام میدهد و میتواند یک کد وضعیت مشخص را بررسی کند.
از پایگاه داده نسخه پشتیبان تهیه کنید، نه از container. یک command آن را dump میکند.
sudo docker compose exec -T database pg_dump -U shlink shlink | gzip > shlink-$(date +%F).sql.gzاین فایل بههمراه فایل compose، کل سرویس را روی یک server جدید بازسازی میکند. ارتقاها با sudo docker compose pull و سپس sudo docker compose up -d انجام میشوند و Shlink هر migration جدید را هنگام start اجرا میکند. پیش از pull کردن، dump را تهیه کنید، زیرا migration را نمیتوان rollback کرد.
FAQ
چرا پس از افزودن reverse proxy، لینکهای کوتاه من پاسخ 404 برمیگردانند؟
Shlink کد کوتاه را با دامنه موجود در هدر Host تطبیق میدهد. اگر proxy نام خودش یا یک نشانی داخلی را ارسال کند، Shlink آن کد را زیر دامنهای جستوجو میکند که هیچ لینکی ندارد؛ بنابراین پاسخ 404 برمیگرداند. proxy_set_header Host $host; را در بلوک location مربوط به nginx تنظیم کنید و proxy را reload کنید. لینکها بلافاصله کار میکنند و نیازی به restart کردن container نیست.
آیا به Postgres نیاز دارم، یا SQLite کافی است؟
SQLite برای آزمایش Shlink مناسب است و به container دوم نیاز ندارد. پیش از انتشار لینکهای مهم، به Postgres مهاجرت کنید؛ زیرا با هر کلیک، ردیفهای بازدید بیشتر میشوند و SQLite عملیات نوشتن را بهصورت سریالی انجام میدهد. تغییر در آینده مستلزم export و import دوباره لینکها است؛ بنابراین انتخاب Postgres از ابتدا، این migration را حذف میکند.
آیا میتوانم API key را که فراموش کردهام کپی کنم، بازیابی کنم؟
خیر. Shlink یک hash از key را ذخیره میکند؛ بنابراین api-key:list نامها و وضعیت را نمایش میدهد، اما هرگز مقدار key را نشان نمیدهد. با shlink api-key:generate یک جایگزین ایجاد کنید، آن را در web client وارد کنید، سپس با shlink api-key:disable key قدیمی را غیرفعال کنید تا دیگر کار نکند.
چرا ستونهای کشور در آمار بازدید من خالی هستند؟
Geolocation به پایگاه داده GeoLite2 نیاز دارد و Shlink آن را فقط زمانی دانلود میکند که یک GEOLITE_LICENSE_KEY در اختیارش بگذارید. دریافت key از MaxMind رایگان است. آن را به بخش environment اضافه کنید، container را دوباره ایجاد کنید تا بازدیدهای جدید مکانیابی شوند. بازدیدهایی که پیش از آن ثبت شدهاند خالی میمانند تا زمانی که shlink visit:locate را اجرا کنید.
چگونه Shlink را به سرور دیگری منتقل کنم؟
دامنه را حفظ کنید و دادهها را منتقل کنید. با pg_dump از پایگاه داده dump بگیرید، dump و فایل compose را به سرور جدید کپی کنید، stack را start کنید، سپس پیش از ورود ترافیک واقعی، dump را در پایگاه داده خالی restore کنید. رکورد DNS را در آخر تغییر دهید. کدهای کوتاه و سابقه بازدید آنها حفظ میشوند، زیرا همهچیز در پایگاه داده قرار دارد.