أساسيات Docker Compose لخادم VPS
ثبّت Docker Engine وCompose v2 على Ubuntu 24.04، واكتب ملف compose.yml حقيقيًا بخدمتين، وتفادَ فخ نشر منافذ Docker حول ufw، وانسخ وحدات التخزين احتياطيًا.
ما الذي ستبنيه
Docker Compose هو الأساس الذي يقوم عليه كل ما عداه تقريبًا على هذا الموقع. Nextcloud وVaultwarden وn8n وImmich وRocket.Chat — كل دليل من هذه الأدلة يبدأ بعبارة «اكتب ملف compose هذا»، وهذه هي الصفحة التي تشرح ما يعنيه ذلك الملف فعلًا. ستثبّت Docker Engine وإضافة Compose v2 من مستودع apt الخاص بـ Docker نفسه على Ubuntu 24.04، ثم تُقيم حزمة حقيقية بخدمتين — Miniflux، قارئ RSS صغير، إلى جانب PostgreSQL — لأن هذا الثنائي يستعرض كل نمط تستخدمه التطبيقات الأكبر: صور مثبّتة على إصدار محدد، وقاعدة بيانات مزوّدة بفحص صحة (healthcheck)، ووحدة تخزين مسمّاة، وأسرار في ملف .env، ومنفذ منشور على localhost فقط.
يستغرق التثبيت خمس دقائق. أما بقية هذا الدليل فتتناول الأجزاء التي تؤلمك لاحقًا: مجموعة docker التي هي root تحت اسم آخر، والمنافذ المنشورة التي تمرّ مباشرة متجاوزة قواعد ufw لديك، والخيار الوحيد في docker compose down الذي يحذف قاعدة بياناتك من دون أي طلب تأكيد.
المتطلبات المسبقة: خادم VPS جديد بتقنية KVM يعمل بنظام Ubuntu 24.04، ومستخدم يملك صلاحية sudo، وغيغابايت واحد من ذاكرة RAM أو أكثر. وجود تثبيت Docker مسبقًا لا بأس به أيضًا — يتناول القسم الأول ما ينبغي إزالته.
ثبّت من مستودع Docker، لا من مستودع Ubuntu
هناك خطآن شائعان يجب تجنّبهما قبل الأمر الأول. حزمة docker.io الخاصة بـ Ubuntu تعمل فعلًا، لكنها متأخرة عن إصدارات Docker ولا تملك بنية الإضافات (plugins) التي يفترضها كل شيء آخر. أما الملف التنفيذي المستقل docker-compose — ذاك الذي فيه شرطة — فهو Compose v1: مكتوب بلغة Python، وانتهى عمره الافتراضي منذ 2023، وهو سبب تعطّل الدروس القديمة. أما Compose اليوم فهو docker compose بمسافة، إضافة (plugin) لواجهة سطر الأوامر، تُثبَّت من المستودع نفسه الذي يأتي منه المحرك.
إذا كان أي من ذلك مثبّتًا على الجهاز مسبقًا، فأزله أولًا — بما في ذلك docker-compose-v2، نسخة Ubuntu الخاصة من الإضافة، بحيث يأتي كل شيء من مستودع واحد:
sudo apt remove -y docker.io docker-compose docker-compose-v2 docker-doc 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 v2.x.x يؤكد أن لديك الإضافة (plugin)، لا الملف التنفيذي الميت من الإصدار v1. أما تشغيل hello-world فينبغي أن ينتهي بـ Hello from Docker!. ويُفعّل التثبيت الخدمة عند الإقلاع؛ ويطبع الأمر systemctl is-enabled docker القيمة enabled.
مجموعة docker هي root — قرّر وأنت مدرك لذلك تمامًا
الآن، كل أمر docker يحتاج إلى sudo، لأن مقبس (socket) الخدمة الخلفية (daemon) عند /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عضوية المجموعة تُطبَّق عند تسجيل الدخول، لذا يبقى الخطأ قائمًا في الجلسة (shell) الحالية. نفّذ newgrp docker لهذه الجلسة، أو سجّل الخروج ثم عاود الدخول؛ وعندها يفترض أن يُدرج الأمر id كلمة docker ضمن مجموعاتك.
والآن الجزء الصريح، بلا مواربة: عضوية مجموعة docker هي root على الجهاز المضيف. ليست «شبه root»، ولا «صلاحيات مرتفعة» — بل root تمامًا. أي شخص في هذه المجموعة يستطيع تنفيذ docker run --rm -it -v /:/host alpine chroot /host والاستيلاء على نظام الملفات كله، من دون أن يُطلَب منه أي كلمة مرور. هذه المجموعة موجودة من أجل الراحة، لا من أجل الاحتواء.
البديل الحقيقي هو وضع Docker بلا صلاحيات الجذر (rootless) — حيث تعمل الخدمة الخلفية نفسها بصلاحيات مستخدمك غير المميّز. وهذا له ثمن: المنافذ الأقل من 1024 تحتاج إلى إعداد إضافي، وتمر الشبكة عبر طبقة محاكاة (shim) في مساحة المستخدم بكلفة أداء ملموسة، وبعض الصور تسيء التصرف من دون root حقيقي. وعلى خادم VPS بمسؤول واحد يملك أصلًا صلاحية sudo عبر الدخول الوحيد المتاح، لا تغيّر هذه المجموعة شيئًا من الناحية العملية، وهذا بالضبط ما تفترضه كل الأدلة هنا — لكن لا تمنحها أبدًا لأحد وكأنها أقل شأنًا من sudo.
تشريح ملف compose
امنح كل حزمة (stack) مجلدها الخاص — فاسم المجلد يصبح اسم المشروع، وهو ما يُستخدم بادئة لأسماء الحاويات والشبكات ووحدات التخزين:
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. الوسم (tag) ليس مجمّدًا: فـ :latest يعاد ربطه في كل مرة تسحب فيها الصورة بأحدث ما دفعه القائم على الصيانة. اجمع ذلك مع عادة الترقية الروتينية التي أنت على وشك تعلّمها — docker compose pull && docker compose up -d — وستجد أن :latest يعني أن القفزات بين الإصدارات الرئيسية تصلك في اللحظة التي ينشرها فيها المصدر الأصلي، لا في اللحظة التي تختارها أنت. ومع PostgreSQL هذا ليس افتراضًا نظريًا: قفزة مفاجئة من 16 إلى 17 تترك الحاوية تدور في حلقة تعطّل متكررة بسبب مجلد بيانات غير متوافق، لأن الترقيات الرئيسية في Postgres تتطلب تفريغًا (dump) واستعادة، لا مجرد إعادة تشغيل.
ثبّت على الأقل الإصدار الرئيسي (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) أمام كل ما يجب أن يواجه العالم. وهذا بالضبط ما يبنيه دليل الوكيل العكسي Traefik بوصفه الخطوة التالية بعد هذه الصفحة — حاوية واحدة تملك المنفذين 80 و443 وتوجّه كل شيء آخر حسب اسم المضيف، مع TLS. (أتيت من إعداد قديم بإصدار Traefik v2؟ يغطي دليل ترحيل Traefik من v2 إلى v3 إعادة التسمية وتغييرات القواعد.)
تحقّق من الربط بعد تشغيل الحزمة: يجب أن يُظهر sudo ss -tlnp | grep 8080 القيمة 127.0.0.1:8080، لا 0.0.0.0:8080 ولا *:8080.
وحدات التخزين المسمّاة مقابل التركيبات المرتبطة (bind mounts)
db-data:/var/lib/postgresql/data وحدة تخزين مسمّاة (named volume): ينشئ Docker مجلدًا تحت /var/lib/docker/volumes/ ويديره، ثم يركّبه داخل الحاوية. أما البديل فهو تركيب مرتبط (bind mount)، مثل ./data:/var/lib/postgresql/data، الذي يربط مسارًا اخترته أنت على المضيف.
والتقسيم العملي الذي يصمد فعليًا: وحدات تخزين مسمّاة للبيانات التي لا تلمسها إلا الحاويات — قواعد البيانات في المقام الأول، لأن Docker يهيّئ وحدة التخزين بالملكية التي تتوقعها الصورة فتعمل أذونات الملفات من تلقاء نفسها. وتركيبات مرتبطة للملفات التي تلمسها أنت من المضيف — ملفات الإعداد التي تحررها بمحرر نصوص، ومكتبة وسائط تزامنها بـ rsync، وأي شيء تريد أن يكون مساره واضحًا. والعطل الكلاسيكي في التركيبات المرتبطة هو الملكية: الحاوية تعمل بمعرّف UID رقم 999، بينما مجلدك على المضيف مملوك للمعرّف UID رقم 1000، فيموت التطبيق عند بدء التشغيل مع ظهور permission denied في سجلّاته. وحدات التخزين المسمّاة تجعل هذه الفئة من الأعطال تختفي في الغالب، على حساب أن تعيش البيانات في مسار يديره Docker — وهذا ما نتناوله أدناه.
environment و.env — أبقِ الأسرار بعيدًا عن git
لا تُقرأ ${POSTGRES_PASSWORD} من الـ shell لديك؛ بل يستكملها Compose (interpolate) من ملف باسم .env موضوع بجانب compose.yml. أنشئه:
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 هو سر عليك تدويره (rotate). وإذا شغّلت الحزمة مع غياب متغير ما، يحذّرك Compose بصوت عالٍ ويواصل العمل بسلسلة فارغة — وهو ما يعني، بالنسبة إلى كلمة مرور Postgres، نشرًا معطوبًا:
WARN[0000] The "POSTGRES_PASSWORD" variable is not set. Defaulting to a blank string.يطبع الأمر docker compose config الملف بعد استكمال كل القيم فيه — وهذه أسرع طريقة للتحقق مما ستستقبله الحاويات فعليًا؛ وتذكّر أن مخرجاته تتضمن أسرارك.
depends_on لا تنتظر شيئًا — إلا إذا أضفت healthcheck
إن depends_on: [db] المجرّدة تتحكم في ترتيب البدء فقط: يشغّل Compose Postgres أولًا ثم التطبيق بعد لحظة، بينما لا يزال Postgres على بعد ثوانٍ من قبول الاتصالات. يصطدم التطبيق بقاعدة البيانات، فيفشل، ثم إما ينهار أو يعيد المحاولة بحسب جودة كتابته.
أما النسخة الموثوقة فهي ما يستخدمه الملف أعلاه: خدمة db تعرّف healthcheck (وPostgres يوفر pg_isready لهذا الغرض بالتحديد)، والتطبيق يعلن depends_on بشرط condition: service_healthy. يبدأ Compose قاعدة البيانات، ويستطلع الفحص كل 10 ثوانٍ، ولا يبدأ Miniflux إلا بعد نجاح الفحص. وإذا لم تصبح قاعدة البيانات سليمة أبدًا — كلمة مرور خاطئة، أو وحدة تخزين تالفة — فلن يبدأ التطبيق أبدًا وسيخبرك Compose بأي تبعية فشلت:
dependency failed to start: container miniflux-db-1 is unhealthyتوجّهك هذه الرسالة إلى docker compose logs db، حيث يعيش الخطأ الحقيقي.
restart: unless-stopped
إعداد restart: unless-stopped على كلتا الخدمتين يعني أن الحاويات تعود بعد أي انهيار وبعد إعادة إقلاع خادم VPS، لكنها تبقى متوقفة إن نفّذت عمدًا docker compose stop. أما البديل always فيحيي الحاويات حتى بعد إيقاف يدوي — وهو نادرًا ما يكون ما تقصده. ومن دون سياسة إعادة تشغيل، تُسقط إعادة إقلاع لتحديث النواة في الساعة الرابعة فجرًا خدماتك بصمت إلى أن تلاحظ ذلك.
الأفعال اليومية
كل ما تحتاجه يوميًا هو خمسة أوامر، تُنفَّذ من مجلد المشروع.
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 آمن التكرار — فهو يقارن الملف بالواقع ولا يمسّ إلا الخدمات التي تغيّر إعدادها أو صورتها. أما ثنائي الترقية فيجلب كل ما تشير إليه وسومك (tags) المثبَّتة الآن: إصدارات تصحيحية تحت postgres:16-alpine، ولا شيء لتثبيت دقيق حتى تعدّله بنفسك — وهذا بالضبط المقصود. تتراكم الصور القديمة بعد الترقيات؛ استرجع مساحة القرص بالأمر docker image prune -f.
والآن الأمر المدمِّر، بصوت عالٍ: docker compose down آمن — فالحاويات والشبكة أشياء يمكن التخلص منها، وبياناتك في وحدة التخزين. أما docker compose down -v فيحذف وحدات التخزين المسمّاة أيضًا. أي أن قاعدة بياناتك تختفي فورًا، من دون أي طلب تأكيد ومن دون أي إمكانية للتراجع. الخيار -v موجود لهدم التجارب؛ أما على حزمة تحمل بيانات حقيقية فعامِله كما تعامل 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 إلى وحدة تخزين فارغة جديدة، بالتركيبات نفسها لكن معكوسة.
وثمة تحفّظ واحد بخصوص قواعد البيانات: أرشفة مجلد بيانات Postgres وهو قيد التشغيل قد تلتقط حالة في منتصف عملية كتابة، فلا يبدأ بعدها بسلامة. إما أن تنفّذ docker compose stop طوال الثواني التي يستغرقها الأرشفة، أو — الأفضل — أن تأخذ تفريغًا منطقيًا (logical dump)، وهو متّسق بحكم بنيته:
docker compose exec -T db pg_dump -U miniflux miniflux | gzip > miniflux-$(date +%F).sql.gzيعطّل الخيار -T الطرفية الوهمية (pseudo-terminal) التي يخصصها 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: مسافتان بادئتان، مسافات فقط، وأي حرف tab في أي موضع قاتل. يتحقق docker compose config من الملف من دون تشغيل أي شيء، وتنفيذه بعد كل تعديل عادة زهيدة التكلفة.
مفاجأة ufw لا تطبع أي خطأ على الإطلاق، وهذا ما يجعلها خطيرة: النشر يعمل، ويبدو ufw status سليمًا، ومع ذلك يعثر مسح للمنافذ من الخارج على قاعدة بياناتك رغم كل شيء. أعد قراءة قسم المنافذ أعلاه، وتحقق من كل مدخل ports: بحثًا عن بادئة 127.0.0.1: مفقودة، وتأكد من جهاز آخر بالأمر curl http://your-vps-ip:8080 — والرد الذي تريده هو رفض الاتصال (connection refused).
من هنا، يحوّل دليل Traefik هذه الحزمة الواحدة إلى تطبيقات عديدة خلف نقطة دخول HTTPS واحدة، ويشكّل ما يستحق الاستضافة الذاتية في 2026 قائمة التسوق التي تمررها عبره.
وخادم لعبة مثل خادم Minecraft على VPS أول مشروع Compose ودود تتمرّن عليه.
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 المجرّد — فهو يزيل الحاويات وشبكة المشروع فقط؛ وتصمد وحدات التخزين المسمّاة ويعيد up -d التالي ربطها من جديد. أما docker compose down -v فهو الصيغة المدمِّرة: يحذف وحدات التخزين المسمّاة، أي قاعدة بياناتك، من دون تأكيد ومن دون تراجع. لا تنفّذ -v أبدًا على حزمة تحمل بيانات حقيقية إلا إن كانت لديك نسخة احتياطية موثّقة.
ما الفرق بين docker-compose وdocker compose؟
docker-compose (بشرطة) هو Compose v1، ملف تنفيذي مستقل مكتوب بلغة Python بلغ نهاية عمره الافتراضي في 2023 ولا ينبغي تثبيته على خوادم جديدة. أما docker compose (بمسافة) فهو Compose v2، إضافة (plugin) مكتوبة بلغة Go لواجهة سطر أوامر Docker، تُثبَّت باسم docker-compose-plugin من مستودع apt الخاص بـ Docker. الأوامر وملفات YAML متوافقة بالكامل تقريبًا، فحين يقول درس قديم docker-compose up، اكتب أنت docker compose up.
لماذا أستطيع الوصول إلى حاوية Docker من الإنترنت رغم أن ufw يحجب المنفذ؟
لأن Docker ينشر المنافذ بقواعد DNAT في سلسلة PREROUTING الخاصة بـ iptables، وتسلك الحزم المعاد كتابتها مسار FORWARD عبر سلاسل Docker الخاصة به — فلا تصل أبدًا إلى سلسلة INPUT حيث تُطبَّق قواعد ufw. لذلك لا يفعل ufw deny 8080 شيئًا مع منفذ حاوية منشور. أصلح الأمر من مصدره: انشر على 127.0.0.1: واعرض الخدمات عبر وكيل عكسي بدلًا من ذلك.
هل أستخدم وحدة تخزين مسمّاة أم تركيبًا مرتبطًا (bind mount)؟
وحدات التخزين المسمّاة للبيانات التي لا تلمسها إلا الحاوية — قواعد البيانات خصوصًا، لأن Docker يضبط الملكية التي تتوقعها الصورة فتعمل الأذونات من تلقاء نفسها. والتركيبات المرتبطة للملفات التي تتعامل معها أيضًا من المضيف: الإعدادات التي تحررها، والوسائط التي ترفعها، وأي شيء تريد أن يكون مساره واضحًا. وإذا فشلت حاوية عند بدء التشغيل برسالة permission denied على تركيب مرتبط، فتعارض معرّف UID بين المضيف والحاوية هو أول ما يجب التحقق منه.