0Pricing
Linux Command Line & Bash Scripting Mastery · درس

كتابة Dockerfiles خفيفة ونقاط دخول Shell

أنشئ سكربتات بناء متعددة المراحل وبدائل نقاط دخول متينة مع معالجة الإشارات وقوالب الإعدادات

كتابة Dockerfiles خفيفة ونقاط دخول Shell درس مجاني في Linux Command Line & Bash Scripting Mastery على CoddyKit. هذا هو الدرس 1 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في Linux Command Line & Bash Scripting Mastery، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة Linux Command Line & Bash Scripting Mastery 4 دروس في المجموع.

لماذا تهم ملفات Docker الرشيقة في DevOps

في بيئات الإنتاج، لكل ميغابايت في صورة Docker تكلفة: عمليات سحب أبطأ، ومساحات أكبر للهجوم، ومساحة تخزين مهدرة في السجل. وتُعد ملفات Docker الرشيقة، إلى جانب أغلفة entrypoint المتينة، سمة من سمات ممارسات DevOps الناضجة.

  • البُنى متعددة المراحل تفصل أدوات وقت البناء عن صورة التشغيل النهائية، مما يقلل الحجم بشكل كبير.
  • أغلفة entrypoint هي نصوص shell صغيرة تهيّئ الحاويات: تنشئ ملفات الإعداد من القوالب، وتتحقق من متغيرات البيئة، وتعالج الإشارات، ثم تنفذ العملية الرئيسية أخيرًا.
  • وتشكّل هذه العناصر معًا أساس أحمال الحاويات الموثوقة والقابلة للنقل في Kubernetes وECS والبيئات التي تعمل على العتاد مباشرة.

يغطي هذا الدرس كلا المجالين من البداية إلى النهاية، مع أنماط جاهزة للإنتاج يمكنكم إضافتها مباشرة إلى مساراتكم.

بنية ملف Docker متعدد المراحل

يستخدم ملف Docker متعدد المراحل تعليمات FROM متعددة. وكل مرحلة عبارة عن مجموعة طبقات معزولة؛ إذ تنسخون فقط العناصر التي تحتاجون إليها إلى المرحلة التالية.

  • المرحلة 0 (builder): تثبّت المترجمات، وأدوات تشغيل الاختبارات، وتبعيات البناء.
  • المرحلة 1 (runtime): تبدأ من أساس minimal (مثل alpine أو distroless) وتنسخ الملفات الثنائية المجمّعة أو حزم التطبيق فقط.
  • لا تحتوي الصورة النهائية على gcc أو make أو الشيفرة المصدرية، إلا إذا نسختموها صراحةً.

استخدموا --from=<stage> في COPY لجلب الملفات عبر حدود المراحل. وسمّوا المراحل باستخدام AS <name> لتحسين القراءة، ولاختيار مرحلة محددة باستخدام docker build --target.

# ---- Stage 0: builder ----
FROM golang:1.22-alpine AS builder
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags='-s -w' -o /app/server ./cmd/server

# ---- Stage 1: runtime ----
FROM gcr.io/distroless/static-debian12
COPY --from=builder /app/server /server
ENTRYPOINT ["/server"]

تقليل الطبقات وإبطال ذاكرة التخزين المؤقت

تنشئ كل تعليمة من التعليمات RUN وCOPY وADD طبقة جديدة. وتؤدي التعليمات المرتبة بشكل سيئ إلى إبطال ذاكرة التخزين المؤقت للبناء دون داعٍ، مما يجعل مسارات CI بطيئة.

  • انسخوا ملفات تعريف التبعيات (package.json وgo.mod وrequirements.txt) قبل نسخ الشيفرة المصدرية، حتى يُخزَّن تثبيت التبعيات مؤقتًا بشكل مستقل.
  • اربطوا الأوامر ذات الصلة باستخدام && ونظّفوا الملفات في طبقة RUN نفسها لتجنب ترك ذاكرة حزم مؤقتة في الطبقات الوسيطة.
  • استخدموا --no-cache في مديري الحزم، واحذفوا ملفات القوائم بعد التثبيت.
FROM python:3.12-slim AS builder
WORKDIR /app

# 1. Install deps first (cached until requirements change)
COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt

# 2. Copy source (cache busted only when code changes)
COPY src/ ./src/

FROM python:3.12-slim
COPY --from=builder /install /usr/local
COPY --from=builder /app/src /app/src
WORKDIR /app
CMD ["python", "-m", "src.main"]

أسرار BuildKit وإعادة توجيه SSH

يجب أن لا تظهر السجلات الخاصة ومفاتيح SSH ورموز API مطلقًا في طبقات الصورة. يوفر Docker BuildKit آليتين آمنتين:

  • --secret: يركّب ملف سر داخل خطوة RUN واحدة دون تضمينه في طبقة. ويمكن الوصول إليه عبر /run/secrets/<id>.
  • --ssh: يعيد توجيه مقبس وكيل SSH المضيف إلى عملية البناء، بحيث يمكن لـ git clone المصادقة دون تضمين المفاتيح الخاصة.

فعّلوا BuildKit باستخدام DOCKER_BUILDKIT=1 أو عبر docker buildx build. وتتيح التعليمة # syntax=docker/dockerfile:1 هذه الميزات.

# syntax=docker/dockerfile:1
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./

# Mount NPM token as a secret — never stored in the image
RUN --mount=type=secret,id=npm_token \
    NPM_TOKEN=$(cat /run/secrets/npm_token) \
    npm ci --prefer-offline

# Usage at build time:
# DOCKER_BUILDKIT=1 docker build \
#   --secret id=npm_token,src=~/.npmrc-token \
#   -t myapp:latest .

كتابة غلاف entrypoint متين

غلاف entrypoint هو نص shell يُعيَّن كتعليمة ENTRYPOINT في ملف Docker. وتتمثل مهمته في إعداد بيئة التشغيل قبل تسليم التحكم إلى العملية الرئيسية.

يتبع الغلاف المنظم جيدًا الترتيب التالي:

  • الخطوة 1: عيّنوا set -euo pipefail لكي يؤدي أي فشل إلى الإيقاف المبكر.
  • الخطوة 2: تحقّقوا من متغيرات البيئة المطلوبة، وأوقفوا التنفيذ سريعًا مع عرض رسالة مفيدة.
  • الخطوة 3: أنشئوا ملفات الإعداد من قوالب باستخدام متغيرات البيئة.
  • الخطوة 4: سجّلوا معالجات الإشارات لتنفيذ إيقاف تشغيل سلس.
  • الخطوة 5: exec "$@" — استبدلوا shell بالعملية الرئيسية حتى يكون PID 1 هو التطبيق، لا الغلاف.

تُعد exec النهائية بالغة الأهمية: فبدونها، لا تُمرَّر الإشارات التي يرسلها Kubernetes أو وقت تشغيل Docker إلى العملية الابنة.

#!/usr/bin/env bash
set -euo pipefail

# Step 2: validate required env vars
REQUIRED_VARS=(DATABASE_URL APP_SECRET PORT)
for var in "${REQUIRED_VARS[@]}"; do
  if [[ -z "${!var:-}" ]]; then
    echo "[entrypoint] ERROR: required env var '$var' is not set" >&2
    exit 1
  fi
done

# Step 3: template config (see next scene)

# Step 4: signal handling (see scene after that)

# Step 5: hand off to CMD
exec "$@"

إنشاء قوالب الإعداد باستخدام envsubst

تستبدل envsubst (من حزمة GNU gettext، والمتوفرة في Alpine باسم gettext) العناصر النائبة ${VAR} في ملف قالب بقيم متغيرات البيئة الحالية.

  • ضمّنوا قالب إعداد *.tmpl في الصورة؛ ثم يعرضه غلاف entrypoint عند بدء التشغيل.
  • مرّروا قائمة المتغيرات صراحةً إلى envsubst حتى لا يوسّع بالخطأ علامات الدولار غير المرتبطة، مثل تلك الموجودة في تعبيرات nginx النمطية.
  • اكتبوا الملف الناتج في مسار قابل للكتابة مثل /tmp أو وحدة تخزين إعدادات مخصصة.
#!/usr/bin/env bash
# Template: /etc/nginx/conf.d/app.conf.tmpl contains:
# server { listen ${NGINX_PORT}; server_name ${SERVER_NAME}; ... }

export NGINX_PORT=${NGINX_PORT:-8080}
export SERVER_NAME=${SERVER_NAME:-localhost}

envsubst '${NGINX_PORT} ${SERVER_NAME}' \
  < /etc/nginx/conf.d/app.conf.tmpl \
  > /etc/nginx/conf.d/app.conf

echo "[entrypoint] nginx config rendered:"
grep -E 'listen|server_name' /etc/nginx/conf.d/app.conf

exec "$@"

معالجة الإشارات والإيقاف السلس

تتلقى الحاويات SIGTERM عند إيقافها بواسطة Kubernetes أو ECS أو docker stop. وإذا كان غلاف entrypoint هو PID 1 ولا يمرر الإشارات، فستُقتل العملية الرئيسية باستخدام SIGKILL بعد انتهاء فترة السماح، مما قد يؤدي إلى فقدان الطلبات أو تلف البيانات.

  • استخدموا trap لالتقاط SIGTERM وSIGINT في الغلاف.
  • مرّروا الإشارة إلى معرّف العملية الابنة باستخدام kill -TERM "$child".
  • استخدموا wait "$child" للانتظار حتى تنتهي العملية الابنة، ثم مرّروا رمز خروجها.
  • بدلًا من ذلك، استخدموا exec لاستبدال shell بالكامل؛ عندها يسلّم نظام التشغيل الإشارات مباشرةً إلى العملية الابنة، ولا تكون هناك حاجة إلى trap. وهذا هو النمط المفضّل في الحالات البسيطة.
#!/usr/bin/env bash
set -euo pipefail

# Start main process in background
"$@" &
child=$!

# Forward SIGTERM and SIGINT to the child
trap 'echo "[entrypoint] caught SIGTERM, forwarding..."; kill -TERM "$child"' TERM
trap 'echo "[entrypoint] caught SIGINT, forwarding...";  kill -INT  "$child"' INT

# Wait for child to exit and capture its exit code
wait "$child"
exit $?

استخدام tini كعملية init minimal

عندما تنشئ الحاوية عمليات ابنة، مثل shell الذي يفرّع عمليات عاملة، فإنكم تحتاجون إلى init حقيقي لجمع العمليات اليتيمة. وtini هو ملف ثنائي صغير مخصص للحاويات.

  • أضيفوا tini إلى صورتكم وعيّنوه كغلاف لنقطة الدخول.
  • يجمع العمليات الابنة اليتيمة، ويمرر الإشارات بشكل صحيح، ويخرج برمز حالة العملية الابنة.
  • يأتي Docker مزودًا بـ tini مدمج يمكن تفعيله باستخدام docker run --init، لكن تضمينه في الصورة يضمن اتساق السلوك عبر أوقات التشغيل المختلفة، مثل Kubernetes وECS وغيرها.
FROM node:20-alpine

# Install tini for proper signal handling and zombie reaping
RUN apk add --no-cache tini

WORKDIR /app
COPY --chown=node:node . .
RUN npm ci --omit=dev

USER node

# tini wraps CMD; forwards SIGTERM and reaps zombies
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["node", "server.js"]

التشغيل كمستخدم غير root

تشكل الحاويات التي تعمل بصلاحيات root (UID 0) خطرًا أمنيًا كبيرًا؛ إذ إن الهروب من الحاوية يمنح وصولًا كاملًا إلى المضيف. احرصوا دائمًا على التبديل إلى مستخدم غير مميّز قبل تنفيذ العملية الرئيسية.

  • أنشئوا مستخدمًا ومجموعة نظام مخصصين في ملف Docker باستخدام addgroup / adduser في Alpine أو groupadd / useradd في Debian.
  • غيّروا ملكية ملفات التطبيق باستخدام COPY --chown=appuser:appgroup، فهذا أكثر كفاءة من طبقة RUN chown منفصلة.
  • بدّلوا إلى المستخدم باستخدام تعليمة USER. ويرث غلاف entrypoint وتعليمة CMD هذا المستخدم.
  • يرفض securityContext.runAsNonRoot: true في Kubernetes بدء صورة ما زالت تعمل كمستخدم root.
FROM python:3.12-slim

# Create non-root user
RUN groupadd --gid 1001 appgroup && \
    useradd --uid 1001 --gid appgroup --shell /bin/bash --create-home appuser

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copy app files with correct ownership in a single layer
COPY --chown=appuser:appgroup src/ ./src/
COPY --chown=appuser:appgroup entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh

USER appuser

ENTRYPOINT ["/entrypoint.sh"]
CMD ["python", "-m", "src.main"]

فحوصات الصحة واختبارات الجاهزية في الصورة

تُعرَّف اختبارات الحيوية والجاهزية في Kubernetes ضمن ملفات البيان، لكن يمكنكم أيضًا تضمين HEALTHCHECK في ملف Docker لبيئات docker run المستقلة وDocker Compose.

  • HEALTHCHECK --interval=10s --timeout=3s --start-period=15s --retries=3 CMD ...
  • استخدموا curl --fail أو wget -qO- لخدمات HTTP؛ أما الخدمات غير المعتمدة على HTTP، فاختبروا مقبسًا باستخدام /dev/tcp/localhost/PORT.
  • ثبّتوا ما تحتاجون إليه فقط: ففي الصور distroless، تجنبوا إضافة curl لمجرد إجراء فحوصات الصحة، واستخدموا بدلًا منه ملفًا ثنائيًا مخصصًا للفحص أو ملف الصحة الخاص بالتطبيق.
FROM nginx:1.27-alpine

COPY nginx.conf /etc/nginx/nginx.conf
COPY dist/ /usr/share/nginx/html/

# Lightweight health check using bash TCP pseudo-device
# (no curl needed — works on any image with bash)
HEALTHCHECK --interval=15s --timeout=5s --start-period=10s --retries=3 \
  CMD bash -c 'exec 3<>/dev/tcp/localhost/80 && echo -e "GET /health HTTP/1.0\r\n" >&3 && cat <&3 | grep -q "200 OK"' || exit 1

EXPOSE 80

جمع العناصر معًا: entrypoint مخصص للإنتاج

فيما يلي غلاف entrypoint كامل مخصص للإنتاج، يجمع كل الأنماط الواردة في هذا الدرس: التحقق من البيئة، وإنشاء ملفات الإعداد من القوالب، وتمرير الإشارات، وتسليم التحكم عبر exec. ويُستخدم هذا النمط في خدمات Node.js وPython وGo المصغّرة الفعلية المنشورة على Kubernetes.

  • يُعلَّق على كل قسم ليكون قالبًا موضحًا لذاته.
  • يُحافظ على الغلاف تحت 50 سطرًا؛ إذ ينبغي أن تكون نقاط الدخول بسيطة وقابلة للتدقيق.
  • لاحظوا exec "$@" النهائية: بعد اكتمال كل الإعدادات، يُستبدل shell بعملية التطبيق، فتصبح PID 1 وتتلقى جميع إشارات نظام التشغيل مباشرةً.
#!/usr/bin/env bash
# entrypoint.sh — production-grade container entrypoint shim
set -euo pipefail

# ── 1. Validate required environment variables ──────────────────
REQUIRED=(DATABASE_URL APP_SECRET PORT LOG_LEVEL)
for var in "${REQUIRED[@]}"; do
  [[ -n "${!var:-}" ]] || { echo "[entrypoint] FATAL: $var is not set" >&2; exit 1; }
done

# ── 2. Set safe defaults for optional variables ─────────────────
export HOST=${HOST:-0.0.0.0}
export WORKERS=${WORKERS:-2}

# ── 3. Render config template ───────────────────────────────────
if [[ -f /etc/app/app.conf.tmpl ]]; then
  envsubst '${DATABASE_URL} ${PORT} ${LOG_LEVEL} ${HOST}' \
    < /etc/app/app.conf.tmpl \
    > /etc/app/app.conf
  echo "[entrypoint] config rendered at /etc/app/app.conf"
fi

# ── 4. Wait for dependent services (optional, fast) ─────────────
if [[ -n "${WAIT_FOR_HOST:-}" ]]; then
  echo "[entrypoint] waiting for ${WAIT_FOR_HOST}:${WAIT_FOR_PORT:-5432}..."
  until bash -c "exec 3<>/dev/tcp/${WAIT_FOR_HOST}/${WAIT_FOR_PORT:-5432}" 2>/dev/null; do
    sleep 1
  done
  echo "[entrypoint] dependency ready"
fi

# ── 5. Hand off to CMD (PID 1 becomes the application) ──────────
echo "[entrypoint] starting: $*"
exec "$@"

اختبار المعرفة: معالجة الإشارات في نقاط الدخول

اختبروا مدى فهمكم لمعالجة الإشارات في نصوص entrypoint الخاصة بالحاويات.

مراجعة: ملفات Docker الرشيقة وأغلفة shell لنقاط الدخول

لقد غطيتم كامل مجموعة تقنيات إنشاء الحاويات المخصصة للإنتاج:

  • البُنى متعددة المراحل تستخدم تعليمات FROM متعددة لإبقاء المترجمات وأدوات البناء خارج الصورة النهائية، مما ينتج طبقات تشغيل رشيقة وminimal.
  • ترتيب الطبقات — انسخوا ملفات تعريف التبعيات قبل الشيفرة المصدرية — يزيد من مرات الاستفادة من ذاكرة التخزين المؤقت ويُسرّع مسارات CI.
  • أسرار BuildKit وتركيبات SSH تُبقي بيانات الاعتماد خارج سجل الصورة دون تعطيل عمليات البناء التي تتطلب المصادقة.
  • أغلفة entrypoint تتحقق من متغيرات البيئة، وتنشئ ملفات الإعداد من القوالب باستخدام envsubst، وتسلم التحكم إلى التطبيق عبر exec "$@".
  • معالجة الإشارات تتطلب إما exec، بحيث يكون التطبيق هو PID 1، أو نمطًا صريحًا من trap + kill + wait عند استخدام المهام التي تعمل في الخلفية.
  • يضيف tini جمع العمليات اليتيمة وتمرير الإشارات بشكل صحيح عندما تنشئ الحاوية عمليات متعددة.
  • يكمل المستخدمون غير root وتعليمات HEALTHCHECK صورة آمنة وقابلة للرصد، وجاهزة لأحمال الإنتاج على Kubernetes.

اجمعوا هذه الأنماط باتساق، وستكون صوركم أصغر وأسرع في النشر وأكثر متانة بكثير في ظروف الإنتاج.

الأسئلة الشائعة

هل درس «كتابة Dockerfiles خفيفة ونقاط دخول Shell» مجاني؟

نعم — نص درس «كتابة Dockerfiles خفيفة ونقاط دخول Shell» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Linux Command Line & Bash Scripting Mastery، انتقل إلى CoddyKit PRO. تتضمن دورة Linux Command Line & Bash Scripting Mastery 4 دروس في المجموع.

ماذا ستتعلم في «كتابة Dockerfiles خفيفة ونقاط دخول Shell»؟

أنشئ سكربتات بناء متعددة المراحل وبدائل نقاط دخول متينة مع معالجة الإشارات وقوالب الإعدادات تتمرن على Linux Command Line & Bash Scripting Mastery مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ Linux Command Line & Bash Scripting Mastery؟

لا تُشترط خبرة سابقة. Linux Command Line & Bash Scripting Mastery على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 1 من أصل 4.

كم من الوقت يستغرق درس «كتابة Dockerfiles خفيفة ونقاط دخول Shell»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس Linux Command Line & Bash Scripting Mastery هذا؟

نعم. كل درس في Linux Command Line & Bash Scripting Mastery يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. كتابة Dockerfiles خفيفة ونقاط دخول Shell
  2. إنشاء قوالب الإعدادات باستخدام envsubst وheredocs
  3. برمجة موارد السحابة باستخدام CLI وjq
  4. فحوصات الصحة وبوابات الجاهزية وحلقات الانتظار
← العودة إلى Linux Command Line & Bash Scripting Mastery