קיצור כתובות URL עצמאי עם Shlink ו-Docker Compose
הקימו מקצר כתובות URL עצמאי על VPS עם Shlink ו-Docker Compose: DNS לדומיין קצר, Postgres, מפתחות API, לקוח Web, קודי QR וסטטיסטיקות לחיצות.
מה שאתם בונים
מקצר כתובות URL באירוח עצמי הוא שרת קטן שהופך קישור ארוך לקישור קצר שבבעלותכם, וסופר כל לחיצה עליו. Shlink הוא הבחירה המתאימה: הוא בקוד פתוח, מופץ כתמונת Docker, ומבצע את כל הפעולות הנדרשות במכולה אחת ובמסד נתונים. מדריך זה מגדיר אותו ב-VPS מאחורי דומיין קצר ייעודי, עם HTTPS, מפתח API, קודי QR וסטטיסטיקות לחיצות.
שני רכיבים מעניקים לו תחושה של שירות קיצור כתובות מסחרי. שרת ה-API מטפל בהפניות ומאחסן את הנתונים. לקוח האינטרנט הוא יישום סטטי נפרד שמתקשר עם ה-API מהדפדפן. אפשר להפעיל את שניהם, או להפעיל את ה-API בלבד ולנהל אותו משורת הפקודה.
מספרי הגרסאות המופיעים כאן היו עדכניים ביולי 2026: Shlink 5.1 ו-shlink-web-client 4.8.
הפנו תחום קצר לשרת תחילה
התחום הוא המוצר. s.example.com/abc123 הוא הקישור שאנשים רואים, לכן בחרו שם קצר וקבעו אותו לפני התקנת דבר כלשהו. Shlink שומר את התחום בכל כתובת URL מקוצרת, ושינויו בהמשך יגרום לכך שכל קישור שכבר הפצתם יפסיק לפעול.
צרו רשומת DNS מסוג A אחת עבור התחום הקצר, והפנו אותה לכתובת ה-IPv4 הציבורית של ה-VPS שלכם. הוסיפו גם רשומת AAAA אם לשרת יש IPv6. לאחר מכן ודאו שהתחום נפתר לפני שתמשיכו.
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, ולכן לא ניתן לגשת אליהן מהאינטרנט עד שתגדירו את ה-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.
cd /opt/shlink
sudo docker compose up -d
sudo docker compose logs -f shlinkבהפעלה הראשונה מתבצעות הגירות מסד הנתונים, ולכן היא נמשכת זמן רב יותר מהפעלות מאוחרות יותר. לאחר שהשירות מתייצב, בדקו שהוא מגיב באופן מקומי.
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080/rest/health200 מציין שה-API פעיל ושחיבור מסד הנתונים תקין. 500 כאן נובע כמעט תמיד ממסד הנתונים: DB_PASSWORD ב-.env אינו תואם לערך שעמו Postgres נוצר, משום שתמונת Postgres קוראת את POSTGRES_PASSWORD רק כאשר היא מאתחלת ספריית נתונים ריקה. לעריכת הסיסמה לאחר מכן אין השפעה עד שמסירים את אמצעי האחסון ומפעילים מחדש.
סיים את HTTPS לפניו
Shlink משרת HTTP רגיל ביציאה 8080. יש להגדיר את TLS בשרת 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;
}
}לאחר מכן הנפק את האישור. ההסבר המלא, כולל טיימר החידוש, מופיע במדריך Certbot עבור nginx ב-Ubuntu 24.04.
sudo certbot --nginx -d s.example.comIS_HTTPS_ENABLED: "true" בקובץ compose הוא שמורה ל-Shlink להציג https:// בכתובות ה-URL המקוצרות שהוא מחזיר. הוא אינו מפעיל TLS בעצמו. השאר אותו false מאחורי proxy מסוג HTTPS, וכל קישור שה-API מחזיר יהיה קישור http://, שיבצע לאחר מכן הפניה מחדש. הדבר מוסיף הלוך ושוב אחד ונראה שגוי בלקוח האינטרנט.
יצירת מפתח API
שום רכיב אינו יכול לגשת ל-API ללא מפתח. יש ליצור מפתח באמצעות ה-CLI בתוך הקונטיינר.
sudo docker compose exec shlink shlink api-key:generate --name "web client"הפקודה מציגה את המפתח פעם אחת בלבד. יש להעתיק אותו כעת, משום שהוא נשמר כערך מגובב ואי-אפשר להציגו שוב. shlink api-key:list מציגה את השמות ואת מצב ההפעלה של כל מפתח, אך לעולם לא את המפתח עצמו. כדי לבטל מפתח, יש להשתמש ב-shlink api-key:disable ולציין את השם.
כל קריאת REST כוללת את המפתח בכותרת 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, ותגיות משמשות לקיבוץ קישורים שעבורם תרצו לקבל בהמשך נתונים סטטיסטיים משולבים.
הציגו את הקישורים הקיימים, ולאחר מכן בדקו את התעבורה של קישור אחד.
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 וזקוק לרשומת proxy משלו, או למנהרת SSH אם אינכם מעוניינים לפרסם אותו. בטעינה הראשונה הוא מבקש כתובת שרת ומפתח 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 הוא השטח הריק סביב הקוד בפיקסלים, וגודל התמונה הסופית הוא הגודל בתוספת פי 2 מהשוליים. הוסיפו errorCorrection=Q כדי ליצור קוד שעדיין ניתן לסריקה גם כשהוא מודפס בגודל קטן או מכוסה חלקית.
השארת השירות פעיל
שירות קיצור כתובות עלול להיכשל בשקט. הקישורים מפסיקים להפנות, ואיש אינו מודיע לך על כך, משום שהאדם שלחץ על הקישור הניח שהוא אינו פעיל. הגדירו בדיקת זמינות מול כתובת URL מקוצרת אמיתית, ולא מול דף הבית, ושלחו התראה על כל תגובה שאינה הפניה. מופע Uptime Kuma באירוח עצמי מתאים לכך, והוא יכול לבדוק קוד מצב מסוים.
גבו את מסד הנתונים, ולא את הקונטיינר. פקודה אחת יוצרת dump שלו.
sudo docker compose exec -T database pg_dump -U shlink shlink | gzip > shlink-$(date +%F).sql.gzהקובץ הזה, יחד עם קובץ ה-compose, מאפשרים לבנות מחדש את השירות כולו בשרת חדש. שדרוגים מתבצעים באמצעות sudo docker compose pull ולאחר מכן sudo docker compose up -d, ו-Shlink מריץ בעת ההפעלה את כל פעולות ההגירה החדשות. צרו את ה-dump לפני ביצוע pull, משום שלא ניתן להחזיר הגירה לאחור.
FAQ
מדוע קישורים מקוצרים מחזירים 404 לאחר הוספת פרוקסי הפוך?
Shlink משווה את הקוד המקוצר לדומיין שבכותרת Host. פרוקסי ששולח את שמו שלו או כתובת פנימית גורם ל-Shlink לחפש את הקוד תחת דומיין שאין בו קישורים, ולכן הוא מחזיר 404. הגדירו את proxy_set_header Host $host; בבלוק המיקום של nginx וטענו מחדש את הפרוקסי. הקישורים יתחילו לפעול מיד, ללא הפעלה מחדש של הקונטיינר.
האם Postgres נדרש, או ש-SQLite מספיק?
SQLite מתאים להתנסות ב-Shlink ואינו דורש קונטיינר נוסף. עברו ל-Postgres לפני פרסום קישורים חשובים, מכיוון שרשומות הביקורים גדלות עם כל לחיצה ו-SQLite מסדר כתיבות באופן סדרתי. מעבר מאוחר יותר דורש ייצוא וייבוא מחדש של הקישורים, ולכן בחירה ב-Postgres בתחילת הדרך חוסכת את ההעברה הזו.
האם אפשר לשחזר מפתח API ששכחתי להעתיק?
לא. Shlink שומר גיבוב של המפתח, ולכן api-key:list מציג שמות ומצב, אך לעולם לא את הערך. צרו מפתח חלופי באמצעות shlink api-key:generate, הדביקו אותו בלקוח האינטרנט, ולאחר מכן השביתו את המפתח הישן באמצעות shlink api-key:disable כדי להפסיק את פעולתו.
מדוע עמודות המדינות ריקות בסטטיסטיקת הביקורים?
לזיהוי מיקום גאוגרפי נדרשת מסד הנתונים GeoLite2, ש-Shlink מוריד רק כאשר מספקים לו GEOLITE_LICENSE_KEY. המפתח ניתן ללא תשלום מ-MaxMind. הוסיפו אותו למקטע הסביבה, צרו מחדש את הקונטיינר, וביקורים חדשים יקבלו מיקום. ביקורים שנרשמו לפני כן יישארו ללא נתונים עד שתפעילו את shlink visit:locate.
כיצד מעבירים את Shlink לשרת אחר?
שמרו על הדומיין והעבירו את הנתונים. בצעו ייצוא של מסד הנתונים באמצעות pg_dump, העתיקו את קובץ הייצוא ואת קובץ ה-compose לשרת החדש, הפעילו את ה-stack, ולאחר מכן שחזרו את קובץ הייצוא למסד הנתונים הריק לפני שהתקבל תעבורת רשת ממשית. שנו את רשומת ה-DNS בסוף. הקודים המקוצרים והיסטוריית הביקורים שלהם יישמרו, מכיוון שכל הנתונים נמצאים במסד הנתונים.