SSD Nodes Learn 8GB RAM — $66/سنة
الأدلة Matt Connorبقلم Matt Connor · آخر تحديث في 2026-08-01

اختصار الروابط ذاتيًا باستخدام Shlink وDocker

أنشئ خدمة اختصار روابط خاصة بك على VPS باستخدام Shlink وDocker Compose، مع نطاق قصير وPostgres ومفاتيح API وعميل ويب ورموز 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، لذلك لا يمكن الوصول إليهما من الإنترنت حتى يكتمل إعداد الوكيل العكسي في القسم التالي. يكتب 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/health

تعني 200 أن API يعمل وأن اتصال قاعدة البيانات ناجح. ويشير 500 هنا في الغالب إلى قاعدة البيانات: لا تتطابق DB_PASSWORD في .env مع القيمة التي أُنشئت بها Postgres، لأن صورة Postgres تقرأ POSTGRES_PASSWORD فقط عند تهيئة دليل بيانات فارغ. لا يؤثر تعديل كلمة المرور لاحقًا حتى تزيل وحدة التخزين وتعيد التشغيل.

أنهِ HTTPS أمامه

يقدّم Shlink خدمة HTTP عادية على المنفذ 8080. يجب إنهاء TLS في وكيل عكسي. والإعداد الوحيد المهم هو تمرير اسم المضيف الأصلي. يحدد Shlink النطاق الذي ينتمي إليه الرمز المختصر بقراءة الرأس Host. لذلك، إذا أعاد الوكيل كتابة هذا الرأس، فستظهر استجابات 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:// في عناوين URL المختصرة التي يعيدها. ولا يفعّل هذا الإعداد TLS بحد ذاته. اتركه false خلف وكيل 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 أن المفتاح غير صحيح أو معطّل أو تجاوز تاريخ انتهاء صلاحيته.

إنشاء روابط قصيرة من سطر الأوامر

تُعد واجهة سطر الأوامر أسرع طريقة لإنشاء الروابط، كما أنها مناسبة للبرامج النصية.

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 رابطًا مقروءًا بدلًا من رمز مُنشأ تلقائيًا. تكون المعرفات النصية فريدة لكل نطاق، لذلك تفشل المحاولة الثانية باستخدام معرف نصي مستخدم بدلًا من الكتابة فوق الرابط الأول بصمت. يمكن تكرار --tag، وتُستخدم الوسوم لتجميع الروابط التي ستحتاج إلى إحصاءات موحّدة لها لاحقًا.

اعرض العناصر الموجودة، ثم افحص حركة المرور على أحد الروابط.

sudo docker compose exec shlink shlink short-url:list
sudo docker compose exec shlink shlink short-url:visits docs

يطبع short-url:visits صفًا واحدًا لكل نقرة، مع التاريخ والمُحيل ووكيل المستخدم. تبقى أعمدة البلد والمدينة فارغة ما لم تُعيّن متغير البيئة 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=20

يمثل size العرض بالبكسل، ويقبل قيمة من 50 إلى 1000، والقيمة الافتراضية هي 300. تكون قيمة format إما png أو svg. يمثل margin المساحة الخالية حول الرمز بالبكسل، ويكون قياس الصورة النهائية مساويًا للحجم مضافًا إليه ضعف الهامش. أضف errorCorrection=Q لإنشاء رمز يظل قابلًا للمسح عند طباعته بحجم صغير أو تغطية جزء منه.

إبقاؤه قيد التشغيل

يتعطل نظام تقصير الروابط بصمت. تتوقف الروابط عن إعادة التوجيه، ولا يخبرك أحد بذلك، لأن الشخص الذي نقر على الرابط افترض أنه لم يعد صالحًا. وجّه فحص وقت التشغيل إلى عنوان URL مختصر فعلي، وليس إلى الصفحة الرئيسية، وأطلق تنبيهًا عند ظهور أي استجابة غير إعادة توجيه. تنفّذ نسخة Uptime Kuma المستضافة ذاتيًا ذلك جيدًا، ويمكنها التحقق من رمز حالة محدد.

أنشئ نسخة احتياطية من قاعدة البيانات، وليس من الحاوية. يفرغها أمر واحد.

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 أي عمليات ترحيل جديدة عند بدء التشغيل. أنشئ التفريغ قبل تنفيذ 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.

احتفظ بالنطاق وانقل البيانات. أنشئ تفريغًا لقاعدة البيانات باستخدام pg_dump، وانسخ التفريغ وملف compose إلى الخادم الجديد، وشغّل المكدس، ثم استعد التفريغ في قاعدة البيانات الفارغة قبل وصول حركة مرور فعلية. غيّر سجل DNS أخيرًا. ستبقى الرموز المختصرة وسجل زياراتها، لأن كل شيء موجود في قاعدة البيانات.