أساسيات Docker Compose على خادم VPS
ثبّت Docker Engine وCompose على Ubuntu 24.04، وشغّل Miniflux وPostgreSQL بخدمتين، وتعلّم لماذا تتجاوز المنافذ المنشورة قواعد ufw وكيف تنسخ وحدات التخزين احتياطياً.
ما الذي ستبنيه
يشكّل Docker Compose الأساس الذي تعتمد عليه كل المكونات الأخرى تقريباً في هذا الموقع. تبدأ أدلة Nextcloud وVaultwarden وn8n وImmich وRocket.Chat كلها بالعبارة: «اكتب ملف Compose هذا». وتشرح هذه الصفحة المعنى الفعلي لذلك الملف. ستثبّت Docker Engine والمكوّن الإضافي Compose من مستودع apt الرسمي لـDocker على Ubuntu 24.04، ثم ستشغّل مكدساً حقيقياً يتألف من خدمتين: Miniflux، وهو قارئ RSS صغير، وPostgreSQL. لأن هاتين الخدمتين تستخدمان جميع الأنماط التي تعتمد عليها التطبيقات الأكبر: صور مثبتة الإصدارات، وقاعدة بيانات مع healthcheck، ووحدة تخزين مسماة، وأسرار في ملف .env، ومنفذ منشور إلى localhost فقط.
يستغرق التثبيت خمس دقائق. ويغطي الجزء المتبقي من هذا الدليل الجوانب التي تسبب المشكلات لاحقاً: كون مجموعة docker اسماً آخر لـroot، وتجاوز المنافذ المنشورة لقواعد ufw مباشرة، ووجود راية واحدة في docker compose down تحذف قاعدة بياناتك من دون طلب تأكيد.
المتطلبات الأساسية: خادم VPS جديد يعمل بنظام Ubuntu 24.04 وKVM، ومستخدم لديه sudo، و1 غيغابايت من الذاكرة العشوائية أو أكثر. لا مشكلة في وجود تثبيت Docker مسبقاً؛ إذ يوضح القسم الأول ما يجب إزالته.
التثبيت من مستودع Docker، وليس من مستودع Ubuntu
قبل تنفيذ الأمر الأول، ارفض خيارين خاطئين. تعمل حزمة docker.io الخاصة بـUbuntu، لكنها تتأخر عن إصدارات Docker ولا تتضمن بنية الإضافات التي تفترضها بقية الخطوات. أما الملف التنفيذي المستقل docker-compose، الذي يحتوي اسمه على واصلة، فهو Compose v1 المكتوب بلغة Python. لم يعد Docker يصونه، وهو سبب تعطل الأدلة القديمة. الإصدار الحالي من Compose هو docker compose مع مسافة، وهو CLI plugin يُثبَّت من المستودع نفسه الذي يُثبَّت منه المحرك.
إذا كان أي من ذلك موجوداً على الخادم، فأزله أولاً، بما في ذلك docker-compose-v2 وdocker-buildx، وهما حزمتا Ubuntu الخاصتان بالإضافات، حتى تأتي جميع المكونات من مستودع واحد:
sudo apt remove -y docker.io docker-compose docker-compose-v2 docker-doc docker-buildx podman-docker containerd runcيُعد Package 'docker.io' is not installed, so not removed الناتج المعتاد على VPS جديد. أضف بعد ذلك مستودع Docker وثبّت الحزم:
sudo apt update
sudo apt install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-pluginتحقق من الطبقات الثلاث كلها:
docker --version
docker compose version
sudo docker run --rm hello-worldيطبع أول أمرين سلاسل الإصدار، بينما يؤكد Docker Compose version v5.x.x أنك تملك الإضافة، وليس الملف التنفيذي القديم v1. انتقلت الإضافة مباشرة من v2 إلى v5 في December 2025، متجاوزةً الإصدارين 3 و4 لتجنب الالتباس مع إصدارات تنسيق ملف compose القديم. لذلك، فإن الأدلة التي تعرض v2.x.x هنا تشير إلى الأداة نفسها. يجب أن ينتهي تشغيل hello-world بالقيمة Hello from Docker!. تفعّل الحزمة تشغيل الخدمة عند الإقلاع، ويطبع systemctl is-enabled docker القيمة enabled.
مجموعة docker هي root، فاتخذ قرارك وأنت على دراية كاملة
حالياً، يحتاج كل أمر docker إلى sudo، لأن مقبس البرنامج الخفي في /var/run/docker.sock مملوك لحساب root ولمجموعة docker. من دون العضوية في هذه المجموعة، سيظهر لك خطأ Docker الأكثر بحثاً عنه:
permission denied while trying to connect to the Docker daemon socket at
unix:///var/run/docker.sockالإصلاح المعتاد:
sudo usermod -aG docker $USERتُطبَّق عضوية المجموعة عند تسجيل الدخول، لذلك يستمر الخطأ في جلسة الصدفة الحالية. شغّل newgrp docker لهذه الجلسة، أو سجّل الخروج ثم سجّل الدخول مجدداً؛ وبعد ذلك يجب أن يعرض id المجموعة docker ضمن مجموعاتك.
والآن الجزء الذي يجب توضيحه بصراحة: العضوية في مجموعة docker تعادل امتلاك صلاحيات root على المضيف. ليست «شبيهة بـroot»، وليست «مرتفعة» فقط، بل هي root. يستطيع أي مستخدم في هذه المجموعة تشغيل docker run --rm -it -v /:/host alpine chroot /host والسيطرة على نظام الملفات بالكامل، من دون طلب كلمة مرور. وُجدت المجموعة لتسهيل الاستخدام، لا لتوفير العزل.
يُعد وضع Docker دون root البديل الفعلي، إذ يعمل البرنامج الخفي نفسه بصفته المستخدم غير المميّز الخاص بك. لكنه يفرض قيوداً: تحتاج المنافذ الأقل من 1024 إلى إعداد إضافي، وتعمل الشبكات عبر طبقة وسيطة في مساحة المستخدم مع تكلفة أداء قابلة للقياس، وقد تتصرف بعض الصور بصورة غير صحيحة من دون صلاحيات root فعلية. على VPS يديره مسؤول واحد، حيث يملك تسجيل الدخول الوحيد صلاحية sudo أصلاً، لا تغيّر المجموعة شيئاً من الناحية العملية. وهذا ما تفترضه كل الأدلة هنا، لكن لا تمنح عضويتها لأحد كما لو كانت أقل خطورة من sudo.
بنية ملف Compose
امنح كل stack دليلاً خاصاً به. يصبح اسم الدليل اسم المشروع، ويُستخدم كبادئة لأسماء الحاويات والشبكات وvolumes:
sudo mkdir -p /opt/miniflux && sudo chown $USER /opt/miniflux && cd /opt/minifluxأنشئ compose.yml، وهو الاسم الحديث. لا يزال docker-compose.yml يعمل. تجاهل المفتاح القديم version:، لأنه مهجور، ويصدر Compose تحذيراً عند العثور عليه.
services:
miniflux:
image: miniflux/miniflux:2.2.9
restart: unless-stopped
ports:
- "127.0.0.1:8080:8080"
environment:
- DATABASE_URL=postgres://miniflux:${POSTGRES_PASSWORD}@db/miniflux?sslmode=disable
- RUN_MIGRATIONS=1
- CREATE_ADMIN=1
- ADMIN_USERNAME=admin
- ADMIN_PASSWORD=${ADMIN_PASSWORD}
depends_on:
db:
condition: service_healthy
db:
image: postgres:16-alpine
restart: unless-stopped
environment:
- POSTGRES_USER=miniflux
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- POSTGRES_DB=miniflux
volumes:
- db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD", "pg_isready", "-U", "miniflux", "-d", "miniflux"]
interval: 10s
timeout: 5s
retries: 5
volumes:
db-data:كل سطر أعلاه يمثل قراراً. عالج القرارات واحداً تلو الآخر.
ثبّت إصدارات الصور، لأن :latest مع pull يؤديان إلى ترقية غير مراقبة
استخدم postgres:16-alpine، وليس postgres:latest. لا تكون العلامة ثابتة؛ إذ يعيد :latest حلها إلى الإصدار الذي دفعه المشرف مؤخراً في كل مرة تنفذ فيها pull. وعند دمج ذلك مع عادة الترقية الدورية التي ستتعلمها، أي docker compose pull && docker compose up -d، فإن :latest يعني أن الانتقال بين الإصدارات الرئيسية سيحدث عند إصدارها من upstream، لا عندما تختار أنت ذلك. وهذا ليس احتمالاً افتراضياً مع PostgreSQL: فالانتقال المفاجئ من 16 إلى 17 يترك الحاوية في حلقة إعادة تشغيل بسبب عدم توافق دليل البيانات، لأن ترقية الإصدارات الرئيسية في Postgres تتطلب dump وrestore، لا إعادة تشغيل.
ثبّت الإصدار الرئيسي على الأقل، إذ يتبع postgres:16-alpine إصدارات التصحيح 16.x. وثبّت التطبيقات على إصدار دقيق مثل miniflux/miniflux:2.2.9. راجع صفحة إصدارات المشروع، واستخدم الإصدار الحالي عند كتابة الملف. تصبح الترقية عندئذ تعديلاً من سطر واحد تنفذه عن قصد، ويظهر في git diff.
انشر على 127.0.0.1، لأن Docker يتجاوز ufw
استخدم "127.0.0.1:8080:8080": عنوان المضيف، ومنفذ المضيف، ومنفذ الحاوية. تكتب معظم الدروس "8080:8080"، وهو اختصار لـ 0.0.0.0:8080:8080، أي الاستماع على كل واجهة، بما فيها الواجهة العامة.
هنا تكمن المشكلة، وتواجهها معظم الأنظمة مرة واحدة على الأقل. ينشر Docker المنفذ بكتابة قاعدة DNAT تعيد كتابة وجهة الحزمة إلى عنوان IP الداخلي للحاوية قبل تطبيق التصفية. لذلك تسلك الحزمة مسار FORWARD ولا تمر أبداً عبر INPUT، حيث توجد قواعد ufw. يعرض sudo ufw deny 8080 نجاحاً، ويعرض ufw status أن المنفذ محظور، ومع ذلك تظل الخدمة تستجيب للإنترنت بالكامل. جدارك الناري ليس معطلاً؛ بل يجري تجاوزه حسب التصميم. يشرح لماذا يتجاوز Docker ufw وكيف تصفي حركة مرور الحاويات فعلياً الآلية وحل DOCKER-USER للمنافذ التي يجب أن تبقى عامة.
العادة التي تلغي المشكلة بالكامل هي ربط المنافذ المنشورة بـ 127.0.0.1، ما لم يكن لديك سبب محدد لعدم فعل ذلك، ووضع reverse proxy أمام أي خدمة يجب أن تواجه العالم. وهذا بالضبط ما ينشئه دليل reverse proxy باستخدام Traefik باعتباره الخطوة التالية بعد هذه الصفحة: حاوية واحدة تملك المنفذين 80 و443 وتوجّه الطلبات إلى كل ما عداهما وفق اسم المضيف، مع TLS. إذا كنت تستخدم إعداد Traefik v2 قديماً، فراجع دليل الترحيل من Traefik v2 إلى v3 لمعرفة عمليات إعادة التسمية وتغييرات القواعد.
تحقق من الربط بعد بدء الـstack: يجب أن يعرض sudo ss -tlnp | grep 8080 القيمة 127.0.0.1:8080، لا 0.0.0.0:8080 أو *:8080.
Named volumes مقابل bind mounts
يمثل db-data:/var/lib/postgresql/data named volume. ينشئ Docker دليلاً ويديره ضمن /var/lib/docker/volumes/، ثم يربطه داخل الحاوية. البديل هو bind mount، أي ./data:/var/lib/postgresql/data، الذي يربط مساراً اخترته على المضيف.
التقسيم العملي المستقر هو التالي: استخدم named volumes للبيانات التي تتعامل معها الحاويات فقط، وقواعد البيانات في مقدمتها، لأن Docker يهيئ الـvolume بالملكية التي تتوقعها الصورة، فتعمل صلاحيات الملفات تلقائياً. واستخدم bind mounts للملفات التي تعدلها من المضيف، مثل ملفات الإعداد التي تحررها بمحرر نصوص، أو مكتبة وسائط تنسخ إليها باستخدام rsync، أو أي بيانات تريد أن يكون مسارها واضحاً. أكثر مشكلات bind mount شيوعاً هي الملكية: تشغّل الصورة عمليتها باستخدام UID ثابت؛ فصورة Miniflux تستخدم UID 65534، بينما يخفض postgres:16-alpine العملية إلى UID 70. ويكون دليل المضيف مملوكاً لـUID 1000، فتتوقف application عند بدء التشغيل مع ظهور permission denied في السجلات. تقلل named volumes هذه الفئة من الأخطاء إلى حد كبير، مقابل تخزين البيانات في مسار يديره Docker، كما هو موضح أدناه.
environment و.env، أبقِ الأسرار خارج git
يملأ Compose قيمة ${POSTGRES_PASSWORD} من ملف اسمه .env موجود بجوار compose.yml، ولكن فقط عندما لا يعرّف shell لديك هذا المتغير مسبقاً. فالقيمة المصدّرة في بيئة shell تتغلب على ملف .env. لذلك قد يتغلب متغير export POSTGRES_PASSWORD=... متروك من تجربة سابقة على الملف بصمت، ويعرض docker compose config القيمة التي فازت، كما هو موضح أدناه. أنشئ الملف:
cat > .env <<'EOF'
POSTGRES_PASSWORD=change-me-to-something-long
ADMIN_PASSWORD=change-me-too
EOF
chmod 600 .env
echo ".env" >> .gitignoreولّد قيماً حقيقية باستخدام openssl rand -hex 24. استخدم Hex لا base64 عن قصد: ستدخل كلمة المرور هذه داخل سلسلة اتصال DATABASE_URL، بينما تؤدي الأحرف / و+ و= التي ينتجها base64 إلى كسر تحليل URL. ويظهر الفشل على شكل خطأ مصادقة، لا خطأ بناء جملة، وقد يستغرق اكتشافه أمسية كاملة. أضف سطر .gitignore قبل أول commit: يمكن نشر ملف compose وتخزينه في نظام التحكم بالإصدارات، أما ملف .env فلا يجوز ذلك. والسر الذي وصل إلى سجل git هو سر يجب تدويره. إذا بدأت الـstack مع فقدان متغير، يصدر Compose تحذيراً واضحاً ويتابع باستخدام سلسلة فارغة. وبالنسبة إلى كلمة مرور Postgres، يعني ذلك نشرًا معطلاً:
WARN[0000] The "POSTGRES_PASSWORD" variable is not set. Defaulting to a blank string.يعرض docker compose config الملف بعد إكمال interpolation، وهي أسرع طريقة للتحقق مما ستستلمه الحاويات فعلياً. تذكر أن ناتجه يتضمن أسرارك.
لا ينتظر depends_on شيئاً ما لم تضف healthcheck
يتحكم depends_on: [db] المجرد في ترتيب البدء فقط. يشغّل Compose Postgres أولاً، ثم يشغّل التطبيق بعد لحظة، بينما لا يزال Postgres يحتاج إلى ثوانٍ قبل قبول الاتصالات. يتصل التطبيق بقاعدة البيانات، ويفشل، ثم يتوقف أو يعيد المحاولة وفق جودة تصميمه.
الإعداد الموثوق هو ما يستخدمه الملف أعلاه: يعرّف service db healthcheck، ويأتي Postgres مزوداً بـpg_isready لهذا الغرض تحديداً. ويعلن التطبيق depends_on مع condition: service_healthy. يشغّل Compose قاعدة البيانات، ويفحص الحالة كل 10 ثوانٍ، ولا يشغّل Miniflux إلا بعد نجاح الفحص. إذا لم تصبح قاعدة البيانات سليمة قط، بسبب كلمة مرور خاطئة أو volume تالف، فلن يبدأ التطبيق، وسيخبرك Compose بأي dependency فشلت:
dependency failed to start: container miniflux-db-1 is unhealthyتوجّهك هذه الرسالة إلى docker compose logs db، حيث يوجد الخطأ الفعلي.
restart: unless-stopped
يعني restart: unless-stopped على الخدمتين أن تعود الحاويات بعد crash وبعد إعادة تشغيل VPS، لكنها تظل متوقفة إذا نفذت docker compose stop عمداً. أما البديل always فيعيد تشغيل الحاويات حتى بعد إيقاف يدوي، وهذا نادراً ما يكون المقصود. ومن دون restart policy، قد تؤدي إعادة التشغيل بعد تحديث kernel عند الساعة 4 صباحاً إلى إيقاف خدماتك بصمت حتى تلاحظ ذلك.
الأوامر اليومية
تُنجز جميع العمليات اليومية باستخدام خمسة أوامر، تُشغّلها من مجلد المشروع.
docker compose up -d # create and start; idempotent, recreates only what changed
docker compose ps # status, ports, and health of this project's containers
docker compose logs -f miniflux # follow one service's logs; --tail 100 for recent history
docker compose pull && docker compose up -d # upgrade to the pinned tags
docker compose down # stop and remove containers and the networkيمكن تشغيل up -d بأمان بشكل متكرر؛ إذ يقارن الملف بالحالة الفعلية ولا يلمس إلا الخدمات التي تغيّر إعدادها أو صورتها. يجلب زوج أوامر الترقية ما تشير إليه العلامات المثبّتة حالياً: إصدارات التصحيح تحت postgres:16-alpine، ولا يجلب شيئاً عند استخدام تثبيت دقيق إلى أن تعدّله، وهذا هو المقصود. تتراكم الصور القديمة بعد الترقية؛ استعد المساحة عبر docker image prune -f.
والآن الأمر المدمّر، ونؤكد ذلك بوضوح: docker compose down آمن لأن الحاويات والشبكة قابلة للحذف، بينما توجد بياناتك في volume. يحذف docker compose down -v الـvolumes المسماة أيضاً. هذا يعني حذف قاعدة بياناتك فوراً، من دون مطالبة بالتأكيد ومن دون إمكانية للتراجع. يوجد الخيار -v لإزالة بيئات التجارب؛ أما في stack يحتوي على بيانات فعلية، فتعامل معه كما تتعامل مع rm -rf. لا توجد سلة محذوفات تحت /var/lib/docker/volumes/.
لفتح shell لمرة واحدة داخل حاوية قيد التشغيل: ينقلك docker compose exec db psql -U miniflux إلى قاعدة البيانات، بينما يفتح docker compose exec miniflux sh shell داخل التطبيق.
أين توجد بياناتك فعلياً
تحصل وحدات التخزين المُسمّاة على بادئة المشروع، لذلك تصبح db-data داخل دليل يُسمّى miniflux بالشكل التالي: miniflux_db-data
docker volume ls
docker volume inspect miniflux_db-dataيتضمن خرج inspect السطر المهم:
"Mountpoint": "/var/lib/docker/volumes/miniflux_db-data/_data"ذلك الدليل هو قاعدة البيانات، ومالكه root، وموجود على نظام ملفات المضيف. ويظل موجوداً بعد down والترقيات وإعادة إنشاء الحاويات. وهو أيضاً ما يجب أن تلتقطه النسخ الاحتياطية تحديداً.
نسخ وحدة تخزين مسماة احتياطياً
النمط القياسي هو استخدام حاوية مؤقتة تُحمّل وحدة التخزين للقراءة فقط إلى جانب مجلد على المضيف، ثم تنشئ أرشيف tar عبر المسارين:
docker run --rm \
-v miniflux_db-data:/data:ro \
-v "$PWD":/backup \
alpine:3.22 tar czf /backup/miniflux-db-$(date +%F).tar.gz -C /data .لا تحتاج إلى تثبيت أي شيء، ولا تترك أي عملية قيد التشغيل. أما الاستعادة فهي العملية المعاكسة، إذ تستخدم tar xzf في وحدة تخزين جديدة وفارغة، مع عكس نقاط التحميل نفسها.
هناك ملاحظة مهمة بشأن قواعد البيانات: قد يؤدي إنشاء أرشيف tar من مجلد بيانات Postgres قيد التشغيل إلى التقاط حالة أثناء الكتابة، ولن تبدأ قاعدة البيانات بهذه الحالة بصورة سليمة. أوقف Postgres مؤقتاً عبر docker compose stop طوال المدة التي يستغرقها tar، أو الأفضل من ذلك، أنشئ تفريغاً منطقياً؛ فهو متسق بطبيعته:
docker compose exec -T db pg_dump -U miniflux miniflux | gzip > miniflux-$(date +%F).sql.gzيعطّل -T الطرفية الافتراضية التي يخصصها Compose افتراضياً. فقد يؤدي تمرير مخرجات التفريغ عبر TTY إلى إتلافها. ضع أحد هذين الأمرين في cron، وانسخ الناتج إلى خارج VPS؛ فالنسخة الاحتياطية الموجودة على القرص نفسه الذي يحتوي على البيانات التي تحميها هي نسخة، وليست نسخة احتياطية. يوضّح دليل Nextcloud إجراءً مجدولاً كاملاً يعتمد على هذين النمطين تحديداً.
أنماط الفشل، مع النصوص التي ستظهر لك
permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock، لم تُضف بعد إلى مجموعة docker، أو أنك أُضفت إليها لكن الجلسة أقدم من ذلك. يعرض id مجموعاتك الفعّالة؛ ويُصلح newgrp docker الصدفة الحالية، بينما تؤدي مغادرة الجلسة وتسجيل الدخول مجدداً إلى إصلاح جميع المجموعات.
Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?، هذه مشكلة مختلفة: الـdaemon نفسه متوقف. يوضّح sudo systemctl status docker وsudo journalctl -u docker -n 50 السبب. في VPS، السبب المعتاد هو امتلاء القرص؛ افحص df -h /var/lib/docker أولاً.
Bind for 127.0.0.1:8080 failed: port is already allocated، نشرت حاوية أخرى منفذ المضيف نفسه. يوضّح docker ps أي حاوية هي؛ وغالباً ما تكون حاوية قديمة من docker run تجريبي أُجري قبل أسابيع. إذا كان docker ps نظيفاً، فهناك عملية غير تابعة لـDocker تستخدم المنفذ: يحدّدها sudo ss -tlnp | grep 8080.
yaml: line 14: did not find expected key، يوجد خطأ في المسافة البادئة عند السطر المذكور أو قبله مباشرة. ملفات Compose هي YAML: استخدم مسافتين لكل مستوى، والمسافات فقط، فأي محرف جدولة في أي موضع يسبب فشلاً قاتلاً. يتحقق docker compose config من الملف من دون تشغيل أي شيء، وتشغيله بعد كل تعديل عادة بسيطة وغير مكلفة.
مفاجأة ufw لا تعرض أي خطأ على الإطلاق، وهذا ما يجعلها خطرة: ينجح النشر، ويبدو ufw status صحيحاً، لكن فحص المنافذ من خارج الخادم يعثر على قاعدة بياناتك رغم ذلك. أعد قراءة قسم المنافذ أعلاه، وافحص كل إدخال ports: بحثاً عن غياب البادئة 127.0.0.1:، وتحقق من جهاز مختلف باستخدام curl http://your-vps-ip:8080؛ فالإجابة التي تريدها هي رفض الاتصال.
من هنا، يحوّل دليل Traefik هذه المجموعة الواحدة إلى عدة تطبيقات خلف نقطة دخول HTTPS واحدة، بينما تمثل الخدمات التي تستحق الاستضافة الذاتية في 2026 قائمة الخدمات التي يمكنك تشغيلها من خلالها. بعد تشغيل عدة مجموعات من هذه الخدمات وإنشاء كل واحدة منها نموذج تسجيل دخول خاصاً بها، يدمج خادم SSO مستضاف ذاتياً مثل Authentik هذه النماذج في حساب واحد خلف الـproxy نفسه.
يُعد خادم ألعاب مثل خادم Minecraft على VPS مشروع Compose أولياً مناسباً للتدرب. وإذا كنت تفضّل التعلم باستخدام شيء تفتحه كل يوم، فإن openGym، متتبع تمارين مستضاف ذاتياً عبارة عن مجموعة صغيرة مثبتة على وسم git بدلاً من وسم image، وتتطلب TLS أمامها قبل تسجيل أول passkey. عادةً ما تكون الصور أول ما يريد الناس استعادته من cloud يملكه شخص آخر، وتساعدك مقارنة PhotoPrism وImmich على تحديد الحد الأدنى من RAM وروتين النسخ الاحتياطي اللذين ستلتزم بهما قبل ربط volume بأي منهما. وعندما لا تعود خدمتان كافيتين، فإن إعداد AFFiNE كمساحة عمل على نمط Notion يطبق الأنماط نفسها على أربع حاويات، ويختبر بإنصاف ما إذا أصبحت الوسوم المثبتة وhealthchecks وvolumes المسماة أعلاه عادة لديك.
FAQ
لماذا تظهر رسالة "permission denied while trying to connect to the Docker daemon socket"؟
إما أن مستخدمك ليس عضواً في المجموعة docker، أو أُضيف إليها بعد بدء الجلسة الحالية؛ إذ لا يُطبَّق الانضمام إلى المجموعة إلا عند تسجيل الدخول. شغّل sudo usermod -aG docker $USER، ثم newgrp docker، أو سجّل الخروج ثم سجّل الدخول مجدداً، وتحقق باستخدام id. تمنح هذه المجموعة صلاحيات مكافئة لصلاحيات root على المضيف، لذلك لا تضف إليها إلا المستخدمين الذين كنت ستمنحهم sudo.
هل يحذف docker compose down بياناتي؟
لا يحذف docker compose down العادي البيانات؛ فهو يزيل الحاويات وشبكة المشروع، بينما تبقى volumes المسماة، ويعيد up -d التالي ربطها. أما docker compose down -v فهو الصيغة المدمرة؛ إذ يحذف volumes المسماة، بما فيها قاعدة بياناتك، من دون تأكيد أو إمكانية للتراجع. لا تشغّل -v أبداً على stack يحتوي على بيانات حقيقية، إلا إذا كانت لديك نسخة احتياطية تم التحقق منها.
ما الفرق بين docker-compose وdocker compose؟
docker-compose (بواصلة) هو Compose v1، وهو ملف Python ثنائي مستقل لم تعد Docker تصونه، ولا ينبغي تثبيته على الخوادم الجديدة. أما docker compose (بمسافة) فهو Compose الحالي، بدءاً من v2، ورقمه v5 منذ December 2025، وهو plugin مكتوب بلغة Go لـDocker CLI، ويُثبَّت باستخدام docker-compose-plugin من مستودع apt الخاص بـDocker. الأوامر وملفات YAML متوافقة تقريباً بالكامل، لذلك عندما يذكر tutorial قديم docker-compose up، اكتب docker compose up.
لماذا أستطيع الوصول إلى حاوية Docker من الإنترنت رغم أن ufw يحظر المنفذ؟
لأن Docker ينشر المنافذ باستخدام قواعد DNAT في سلسلة PREROUTING الخاصة بـiptables، ولأن الحزم المعاد توجيهها تسلك المسار FORWARD عبر سلاسل Docker الخاصة، فإنها لا تصل أبداً إلى سلسلة INPUT التي تُطبَّق فيها قواعد ufw. لذلك لا يفعل ufw deny 8080 شيئاً لمنفذ حاوية منشور. أصلح المشكلة من المصدر: انشر المنفذ إلى 127.0.0.1:، وعرّض الخدمات عبر Reverse Proxy بدلاً من ذلك.
هل أستخدم volume مسماة أم bind mount؟
استخدم volumes المسماة للبيانات التي تتعامل معها الحاوية وحدها، وخصوصاً قواعد البيانات، لأن Docker يضبط الملكية التي تتوقعها image وتعمل الصلاحيات مباشرة. استخدم bind mounts للملفات التي تتعامل معها أيضاً من المضيف، مثل ملفات الإعداد التي تعدّلها، والوسائط التي ترفعها، وأي شيء تريد أن يكون مساره واضحاً. إذا فشلت حاوية عند بدء التشغيل مع permission denied على bind mount، فتحقق أولاً من عدم تطابق UID بين المضيف والحاوية.