הקמת מקצר קישורים עצמאי עם Shlink ו-Docker Compose
למדו כיצד להריץ שרת קיצור קישורים אישי ב-VPS באמצעות Shlink ו-Docker Compose. המדריך כולל הגדרות DNS, מסד נתונים Postgres, הפקת מפתחות API, הצגת סטטיסטיקות ושימוש ב-QR.
מה אתם בונים
מקצר קישורים בניהול עצמי הוא שרת קטן שהופך כתובת URL ארוכה לכתובת קצרה שבבעלותכם, וסופר כל לחיצה עליה. Shlink היא הבחירה המומלצת: היא מבוססת קוד פתוח, מופצת כ־Docker image, ומבצעת את כל העבודה במכולה אחת בתוספת מסד נתונים. מדריך זה יתקין אותה על גבי VPS תחת שם מתחם קצר ואמיתי, עם HTTPS, מפתח API, קודי QR וסטטיסטיקות לחיצות.
שני רכיבים מעניקים לה תחושה של שירות קיצור מסחרי. שרת ה־API מטפל בהפניות ומחזיק את הנתונים. ה־web client הוא יישום סטטי נפרד שמתקשר עם ה־API מתוך הדפדפן שלכם. ניתן להריץ את שניהם, או להריץ את ה־API בלבד ולנהל אותו משורת הפקודה.
מספרי הגרסאות המצוינים כאן היו עדכניים נכון ליולי 2026: Shlink 5.1 ו־shlink-web-client 4.8.
הפנו תחום קצר לשרת תחילה
התחום הוא המוצר. s.example.com/abc123 הוא הקישור שאנשים רואים, לכן בחרו שם קצר והחליטו עליו לפני שתתקינו דבר מה. Shlink שומרת את התחום עם כל קישור מקוצר, ושינויו בשלב מאוחר יותר יגרום לכל הקישורים שכבר הפצתם להפסיק לעבוד.
צרו רשומת DNS מסוג A עבור התחום הקצר, המפנה לכתובת ה-IPv4 הציבורית של ה-VPS שלכם. הוסיפו גם רשומת AAAA אם לשרת יש IPv6. לאחר מכן, ודאו שהכתובת מתורגמת (resolves) לפני שתמשיכו.
dig +short s.example.com Aהפלט חייב להיות הכתובת של השרת שלכם. אם הפלט ריק, הרשומה טרם הופצה, וכל שלב הבא ייכשל בצורה מבלבלת, כיוון שלא ניתן להנפיק תעודת TLS (אבטחת שכבת תעבורה) עבור שם שאינו מתורגם.
קובץ ה-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 כותב חוקי ניתוב משלו לפני ה-firewall של המארח, מה שאומר ששורה פשוטה של 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.
cd /opt/shlink
sudo docker compose up -d
sudo docker compose logs -f shlinkההפעלה הראשונה מריצה את ה-migrations של מסד הנתונים, לכן היא אורכת זמן רב יותר מהפעלות הבאות. כשהתהליך מתייצב, ודאו שהשירות מגיב מקומית.
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080/rest/healthקבלת 200 משמעותה שה-API פעיל וחיבור מסד הנתונים תקין. קבלת 500 כאן נובעת כמעט תמיד ממסד הנתונים: ה-DB_PASSWORD בתוך .env אינו תואם לזה ששימש ליצירת ה-Postgres, כיוון שתמונת ה-Postgres קוראת את POSTGRES_PASSWORD רק בעת אתחול ספריית נתונים ריקה. עריכת הסיסמה לאחר מכן לא תשפיע עד שתסירו את ה-volume ותתחילו מחדש.
סיום HTTPS לפני השירות
Shlink מגיש HTTP רגיל בפורט 8080. הצפנת TLS צריכה להתבצע ב-reverse proxy, וההגדרה החשובה ביותר היא העברת שם ה-host המקורי. Shlink קובע לאיזה דומיין שייך קיצור הקישור על ידי קריאת ה-header מסוג Host. לכן, proxy שמשכתב את ה-header הזה יגרום לשגיאות 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;
}
}לאחר מכן, הנפיקו את התעודה. המדריך המלא, כולל טיימר החידוש, נמצא ב-מדריך Certbot עבור nginx ב-Ubuntu 24.04.
sudo certbot --nginx -d s.example.comהערך IS_HTTPS_ENABLED: "true" בקובץ ה-compose הוא מה שגורם ל-Shlink להדפיס https:// בקישורים הקצרים שהוא מחזיר. הגדרה זו אינה מפעילה TLS בעצמה. השאירו את הערך false מאחורי proxy מסוג HTTPS, אחרת כל קישור שה-API מחזיר יהיה קישור מסוג http://, מה שיגרום להפניה נוספת (redirect) – פעולה שצורכת זמן תגובה נוסף ונראית לא תקינה בממשק האינטרנט.
יצירת מפתח API
שום גורם אינו יכול לתקשר עם ה-API ללא מפתח. צרו מפתח באמצעות ה-CLI בתוך המכולה.
sudo docker compose exec shlink shlink api-key:generate --name "web client"הפקודה מציגה את המפתח פעם אחת בלבד. העתיקו אותו כעת, כיוון שהוא נשמר בצורה מוצפנת (hashed) ולא ניתן להציגו שוב. 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 מעיד על כך שהמפתח תקין. קוד 401 הנושא את הערך INVALID_API_KEY מעיד על כך שהמפתח שגוי, מושבת או שפג תוקפו.
יצירת קישורים מקוצרים משורת הפקודה
ממשק שורת הפקודה (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 מאפשר לכם ליצור קישור קריא במקום קוד שנוצר באופן אקראי. ה־slugs הם ייחודיים לכל דומיין, לכן ניסיון ליצור קישור עם slug שכבר קיים ייכשל, במקום לדרוס את הקישור הקיים ללא התראה. ניתן להשתמש ב---tag שוב ושוב, ותגיות (tags) הן הדרך שבה מקבצים קישורים שעבורם תרצו להפיק נתונים סטטיסטיים משותפים בעתיד.
הציגו את רשימת הקישורים הקיימים, ולאחר מכן בדקו את התעבורה של קישור ספציפי.
sudo docker compose exec shlink shlink short-url:list
sudo docker compose exec shlink shlink short-url:visits docsshort-url:visits מדפיס שורה אחת עבור כל הקלקה, הכוללת את התאריך, ה־referrer וסוכן המשתמש (user agent). העמודות של המדינה והעיר יישארו ריקות אלא אם תגדירו את משתנה הסביבה GEOLITE_LICENSE_KEY, שהוא מפתח MaxMind חינמי שבו Shlink משתמש כדי להוריד את מסד הנתונים GeoLite2. ללא הגדרה זו, הביקורים עדיין יתועדו, אך ללא ציון מיקום גיאוגרפי.
לקוח האינטרנט וקודי QR
לקוח האינטרנט זמין כעת ב-127.0.0.1:8081 ודורש הגדרת proxy משלו, או מנהרת SSH אם אינכם מעוניינים לחשוף אותו לציבור. בעת הטעינה הראשונה, הלקוח יבקש כתובת URL של שרת ומפתח API. הזינו את https://s.example.com ואת המפתח שיצרתם. הלקוח שומר את שניהם באחסון הדפדפן ופונה ל-API שלכם ישירות, כך ששום מידע לא עובר דרך גורם שלישי. הפרדת הממשק מה-API היא תבנית שכדאי לשים לב אליה, שכן זו אותה תבנית המאפשרת ל-Halcyon להציג ספריית Jellyfin כחנות השכרת סרטים משנות ה-90 מבלי לשנות את שרת המדיה שמאחוריה.
קודי 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 עבור קוד שניתן לסריקה גם כאשר הוא מודפס בגודל קטן או מכוסה חלקית.
שמירה על פעילות תקינה
שירות קיצור קישורים עלול להיכשל בשקט. הקישורים מפסיקים להפנות ולא תקבלו על כך התראה, כיוון שהמשתמש שלחץ על הקישור מניח פשוט שהקישור אינו תקין. הגדירו בדיקת uptime מול כתובת מקוצרת אמיתית ולא מול דף הבית, והגדירו התראה על כל תוצאה שאינה הפניה (redirect). מופע Uptime Kuma בניהול עצמי מבצע זאת היטב, ויכול לנטר קוד סטטוס ספציפי.
בצעו גיבוי למסד הנתונים, לא למכולה (container). פקודה אחת מבצעת dump לנתונים.
sudo docker compose exec -T database pg_dump -U shlink shlink | gzip > shlink-$(date +%F).sql.gzקובץ זה, יחד עם קובץ ה-compose שלכם, מאפשרים להקים מחדש את כל השירות על שרת חדש. כל יישום בשרת זקוק לגרסה משלו של צמד קבצים זה. ספריית תמונות היא מקרה מורכב, כיוון ש-PhotoPrism ו-Immich מחזיקים את הקבצים המקוריים על הדיסק לצד רשומות במסד הנתונים, כך ש-dump בלבד לא ישחזר דבר. שדרוגים מתבצעים באמצעות sudo docker compose pull ולאחריו sudo docker compose up -d, ו-Shlink מריץ כל מיגרציה חדשה בעת העלייה. בצעו את ה-dump לפני ה-pull, כיוון שלא ניתן לבצע rollback למיגרציה.
FAQ
מדוע הקישורים הקצרים שלי מחזירים שגיאת 404 לאחר הוספת reverse proxy?
Shlink משווה את הקוד הקצר מול הדומיין המופיע ב־header מסוג Host. כאשר ה-proxy שולח את שמו שלו או כתובת פנימית, Shlink מחפש את הקוד תחת דומיין שאין בו קישורים, ולכן הוא מחזיר 404. הגדירו את proxy_set_header Host $host; בתוך בלוק ה-location של nginx וטענו מחדש את ה-proxy. הקישורים יתחילו לעבוד מיד, ללא צורך באתחול המכולה.
האם אני חייב להשתמש ב-Postgres, או ש-SQLite מספיק?
SQLite מתאים לבדיקת Shlink ואינו דורש מכולה נוספת. עברו ל-Postgres לפני פרסום קישורים חשובים, כיוון ששורות הביקורים גדלות עם כל הקלקה ו-SQLite מבצע כתיבות באופן סדרתי. מעבר מאוחר יותר ידרוש ייצוא וייבוא מחדש של הקישורים, לכן בחירה ב-Postgres מלכתחילה תחסוך לכם את תהליך ההגירה.
האם ניתן לשחזר מפתח API ששכחתי להעתיק?
לא. Shlink שומר hash של המפתח, לכן api-key:list מציג שמות וסטטוס אך לעולם לא את ערך המפתח עצמו. צרו מפתח חלופי באמצעות shlink api-key:generate, הדביקו אותו בלקוח ה-web, ולאחר מכן השביתו את המפתח הישן בעזרת shlink api-key:disable כדי להפסיק את פעולתו.
מדוע עמודות המדינה בסטטיסטיקת הביקורים ריקות?
מיקום גיאוגרפי דורש את מסד הנתונים GeoLite2, ש-Shlink מוריד רק כאשר מספקים לו GEOLITE_LICENSE_KEY. המפתח ניתן בחינם מ-MaxMind. הוסיפו אותו לסעיף ה-environment, צרו מחדש את המכולה, וביקורים חדשים ימוקמו גיאוגרפית. ביקורים שנרשמו לפני כן יישארו ריקים עד להרצת shlink visit:locate.
כיצד ניתן להעביר את Shlink לשרת אחר?
שמרו על הדומיין והעבירו את הנתונים. בצעו dump למסד הנתונים בעזרת pg_dump, העתיקו את ה-dump ואת קובץ ה-compose לשרת החדש, הפעילו את ה-stack, ולאחר מכן שחזרו את ה-dump לתוך מסד הנתונים הריק לפני הגעת תעבורה ממשית. שנו את רשומת ה-DNS בסוף התהליך. הקודים הקצרים והיסטוריית הביקורים שלהם יישמרו, כיוון שהכול נמצא במסד הנתונים.