كتابة Dockerfiles خفيفة ونقاط دخول Shell
أنشئ سكربتات بناء متعددة المراحل وبدائل نقاط دخول متينة مع معالجة الإشارات وقوالب الإعدادات
كتابة Dockerfiles خفيفة ونقاط دخول Shell درس مجاني في DevOps Bootcamp على CoddyKit. هذا هو الدرس 1 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في DevOps Bootcamp، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة DevOps Bootcamp 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) وفتح باقي دورة DevOps Bootcamp، انتقل إلى CoddyKit PRO. تتضمن دورة DevOps Bootcamp 4 دروس في المجموع.
ماذا ستتعلم في «كتابة Dockerfiles خفيفة ونقاط دخول Shell»؟
أنشئ سكربتات بناء متعددة المراحل وبدائل نقاط دخول متينة مع معالجة الإشارات وقوالب الإعدادات تتمرن على DevOps Bootcamp مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ DevOps Bootcamp؟
لا تُشترط خبرة سابقة. DevOps Bootcamp على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 1 من أصل 4.
كم من الوقت يستغرق درس «كتابة Dockerfiles خفيفة ونقاط دخول Shell»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس DevOps Bootcamp هذا؟
نعم. كل درس في DevOps Bootcamp يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- كتابة Dockerfiles خفيفة ونقاط دخول Shell
- إنشاء قوالب الإعدادات باستخدام envsubst وheredocs
- برمجة موارد السحابة باستخدام CLI وjq
- فحوصات الصحة وبوابات الجاهزية وحلقات الانتظار