إنشاء حاوية دوكر لتطبيق ريآكت ورفعها إلى سجل الحاويات في DigitalOcean


إنشاء حاوية دوكر لتطبيق ريآكت ورفعها إلى سجل الحاويات في DigitalOcean June 23, 2025 at 07:00PM

أَحدَثَت مشاريع الحوسبة السحابية الأصيلة Cloud-native Computing تغييرًا كبيرًا في طريقة بناء التطبيقات ونشرها، ووفرت وسائل متنوعة تُسَهِّل العمل، بدايةً من أدوات التكامل Integrating وتحزيم Packaging شيفرة التطبيق تمهيدًا لنشره، ووصولًا إلى أدوات التوسعة Scaling وغير ذلك.

تندرج هذه الأعمال جميعها تحت مظلة DevOps وتُعدّ الحاويات في عصرنا الحالي أشهر مفاهيم DevOps الحديث وأكثرها تأثيرًا، وسنعرض في مقالنا هذا مثالًا عمليًّا عن نشر تطبيق ريآكت React بمساعدة الحاويات؛ إذ سننشئ صورة دوكر Docker للتطبيق ثم نرفعها إلى سجل الحاويات Container Registry وننشرها باستخدام خادم Droplet من منصة DigitalOcean.

متطلبات العمل

من أجل إنشاء حاوية دوكر لتطبيق ريآكت ورفعها إلى سجل الحاويات في DigitalOcean، سنحتاج إلى المتطلبات التالية:

تثبيت Docker

تساعدنا توثيقات دوكر وهذا الدليل على تثبيت دوكر على الجهاز. وعند إتمام التثبيت، سنستخدم الأمر docker --version للاستعلام عن إصدار دوكر والتحقق من نجاح تثبيته، وذلك وفق التالي:

docker --version
Docker version 26.0.0, build 2ae903e

إنشاء تطبيق ريآكت

خطوتنا التالية هي تشغيل تطبيق ريآكت React، فإذا كان التطبيق الذي نخطط لاستخدامه موجودًا على الجهاز المحلي، فلا بد من تشغيله مباشرةً. وإذا كان موجودًا على GitHub، فيمكن استنساخ مستودع التطبيق إلى الجهاز مباشرةً؛ أما في حال لم نكن نمتلك تطبيقًا، فلا بد من إنشاء واحد باستخدام Vite كما يلي:

npm create vite@latest react-app -- --template react

سيُنشئ هذا الأمر تطبيقًا تجريبيًّا ضمن المجلد react-app، وبعدها يمكن الانتقال للمجلد وتشغيل التطبيق بواسطة الأوامر التالية:

cd react-app
npm install
npm run dev

وعند الانتهاء من التشغيل سنحصل على رسالة شبيهة بالتالي:

OutputVITE v5.2.11  ready in 712 ms
  Local:   http://localhost:5173/
  Network: use --host to expose
  press h + enter to show help

يمكن الآن فتح التطبيق من المتصفح بكتابة العنوان localhost:5173 كما يلي:

img01 vite react app

بهذا يكون تطبيقنا قد أصبح جاهزًا الآن، ونستطيع الانتقال للخطوة التالية لنبدأ بتحزيمه ضمن صورة Docker، ثم نشره في بيئة الإنتاج.

إنشاء الملف Dockerfile

يساعدنا دوكر Docker على تغليف شيفرة تطبيقنا ضمن حاوية وتشغيله، وذلك باستخدام صور دوكر وحاويات دوكر.

تُنشَئ صور دوكر باستخدام ملف خاص يدعى Dockerfile يتضمن التعليمات البرمجية اللازمة لبناء الصورة؛ إذ تتكون الصورة image من عدة طبقات layers للقراءة فقط. وعند إضافة أي تعليمة إلى الملف Dockerfile، تُضَاف طبقة جديدة إلى الصورة.

تُخَزَّن هذه الطبقات بهيئة قيم مُعَمَّاة hash من نوع SHA-256، ويمكن معرفة المزيد عن بنية صور الحاويات بمطالعة مقال ما هي صورة الحاوية container image؟

إذًا تُبنى صور دوكر بإنشاء الملف Dockerfile ويشمل ذلك خطوتين أساسيتين:

  • بناء التطبيق
  • إعداد خادم nginx وتشغيله لخدمة التطبيق

تنويه: ستُكتَب جميع التعليمات التي ننفذها في هاتين الخطوتين ضمن الملف Dockerfile.

1. بناء التطبيق

سنوجه Docker في البداية ليستخدم أحدث نسخة من Node.js لتكون الركيزة التي سيبني عليها تطبيق React وفق التالي:

FROM node:latest AS builder

بعد ذلك سنحَدِّد مجلد العمل WORKDIR الذي ستُنَفَّذ فيه جميع التعليمات اللاحقة، والذي سيكتمل فيه تطبيقنا:

WORKDIR /app

سننسخ بعدها الملف package.json إلى الحاوية builder ونشَغِّل npm install لتثبيت جميع الاعتماديات المطلوبة في الملف package.json وبذلك نضمن أن تطبيق React يمتلك كل ما يحتاجه ليعمل بصورة سليمة:

COPY package.json .
RUN npm install

والآن. يمكن نسخ بقية ملفات المشروع إلى الحاوية وتشغيل npm run build لتصريف compile التطبيق وفق الأمر التالي الذي سيُحَوِّل الشيفرة المصدرية لتطبيقنا إلى حزمة bundle جاهزة لبيئة الإنتاج ومُخَزَّنة في المجلد /dist ومُضبوطة لتعمل بكفاءة وأداء جيد:

COPY . ./
RUN npm run build

2. إعداد خادم nginx للتطبيق وتشغيله

يشتهر NGINX بالكفاءة والسرعة، لذا يُعدّ خيارًا مناسبًا للاستخدام كخادم ويب لتطبيقنا حتى يصله بالمستخدمين، وسنعتمد لبنائه أحدث صورة nginx متوفرة؛ ثم نستبدل ملف الإعدادات الافتراضي الذي يأتي مع الصورة default.conf بملف إعدادات مشروعنا ليتمكن خادم nginx من تلبية طلبات المستخدمين الواردة إلى التطبيق.

وذلك وفق الأوامر التالية:

FROM nginx:latest
RUN rm -rf /etc/nginx/conf.d/default.conf
COPY ./nginx/default.conf /etc/nginx/conf.d/default.conf

سننسخ بعد ذلك تطبيق ريآكت الذي حوّلناه إلى حزمة في الخطوة السابقة، وذلك من المجلد dist في الحاوية builder إلى المجلد الذي يتوقع nginx أن يجد ملفات تطبيقنا فيه، ليستطيع خدمة المستخدمين، وذلك وفق الأمر التالي:

COPY --from=builder /app/dist /usr/share/nginx/html

سنضبط بعد ذلك قيمة مجلد العمل WORKDIR داخل حاوية nginx ليشير إلى مجلد التطبيق:

WORKDIR /usr/share/nginx/html

ثم نشَغِّل خادم nginx بواسطة الأمر التالي، الذي يوجه Docker لبدء تشغيله والحفاظ عليه في حالة عمل:

CMD ["/bin/bash", "-c", "nginx -g "daemon off;""]

إذا جمعنا كل التعليمات السابقة معًا، فستشكل الملف Dockerfile:

# ------------------------
# Step 1: Build react app
# ------------------------

# Use node:latest as the builder image
FROM node:latest AS builder

# Set the working directory
WORKDIR /app

# Copy package.json and install app dependencies
COPY package.json .
RUN npm install

# Copy other project files and build
COPY . ./
RUN npm run build

# --------------------------------------
# Step 2: Set up nginx to serve the app
# --------------------------------------
# Use nginx:latest as the base image
FROM nginx:latest

# Overwriting nginx config with our own config file
RUN rm -rf /etc/nginx/conf.d/default.conf
COPY ./nginx/default.conf /etc/nginx/conf.d/default.conf

# Copy over the build created in the Step 1
COPY --from=builder /app/dist /usr/share/nginx/html

# Set the working directory
WORKDIR /usr/share/nginx/html

# Start nginx server
CMD ["/bin/bash", "-c", "nginx -g \"daemon off;\""]

لذا سننشئ ملفًا نصيًّا باسم Dockerfile ضمن المجلد الجذر لتطبيقنا كما يلي:

touch Dockerfile

مع نسخ التعليمات السابقة ضمنه.

إنشاء ملف إعدادات NGINX

ذكرنا في الخطوة السابقة الخاصة بإعداد خادم NGINX أننا سنستبدل ملف الإعدادات الافتراضي للخادم بملف إعدادات مشروعنا، فكيف ننشئ ملف إعدادات المشروع؟

سنتوجه في البداية إلى مجلد الجذر لتطبيقنا ثم ننشئ بداخله مجلدًا جديدًا باسم nginx، وننشئ بداخله ملفًا نصيًّا يدعى default.conf كما يلي:

mkdir nginx
cd nginx && touch default.conf

ثم ننسخ الإعدادات التالية والصقها ضمنه:

server {
  listen 80;
  add_header Cache-Control no-cache;
  location / {
    root   /usr/share/nginx/html;
    index  index.html index.htm;
    try_files $uri $uri/ /index.html;
    expires -1;
  }
  error_page   500 502 503 504  /50x.html;
  location = /50x.html {
    root   /usr/share/nginx/html;
  }
}

تُحَدِّد هذه الإعدادات البوابة التي سيستقبل خادم nginx الطلبات عبرها، وهي البوابة 80، وأيضًا الملفات التي سيتعامل معها، بالإضافة إلى الأخطاء الافتراضية التي قد تعترضه مصنفةً حسب رمز الخطأ. وفي هذا المجال، يُنصح بالاطلاع على مقال فهم بنية ملف إعدادات nginx وسياقات الإعدادات، كما توفر DigitalOcean لمستخدميها أداة رسومية تساعدهم في إنشاء ملف إعدادات nginx.

بناء صورة التطبيق image

سنستخدم هننا تعليمة cd للعودة إلى مجلد الجذر لتطبيقنا حيث يوجد الملف Dockerfile، ثم ننفِّذ الأمر التالي:

docker image build -t react-app:v1.0 . --platform linux/amd64

تتميز كل صورة دوكر عن غيرها باسم name ووسم tag خاصين بها يُكتَبان بعد الراية t-؛ فالصورة في مثالنا تدعى react-app، ولها الوسم v1.0؛ أما الراية platform-- فتساعدنا على تحديد المنصة التي تُبنى الصور من أجلها، وتُعَدُّ خيارًا مهمًا في الحالات التي نحتاج فيها لبناء صورة متعددة المنصات cross-platform حتى تُحَِّدد الأنظمة التي ستتوافق معها.

سنحصل على نتيجة تشبه التالي بعد الانتهاء من بناء الصورة:

[+] Building 113.9s (17/17) FINISHED
=> [internal] load build definition from Dockerfile
=> => transferring dockerfile: 982B
...
...
=> => writing image sha256:d125b102b094224c82ddb69f9ece98c7161c8660fe72fd5aec57e41a6d72cf2f
=> => naming to docker.io/library/react-app:v1.0

يمكن استخدام الأمر التالي الذي يعرض صور دوكر الموجودة لتتحقق من صحة بناء الصورة:

docker image list

وسنحصل على النتيجة التالية:

OutputREPOSITORY   TAG   IMAGE ID       CREATED         SIZE
react-app    v1.0  d125b102b094   6 minutes ago   188MB

وكما نلاحظ، فصورة التطبيق التي بنيناها ضمن قائمة الصور الموجودة.

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

لننتقل الآن إلى الخطوة التالية وهي تخزين الصورة في مكان مركزي يُسَهِّل توزيعها ونشرها، وهنا يأتي دور سجل الحاويات Container Registry.

رفع صورة التطبيق إلى سجل الحاويات

سجل الحاويات Container Registry هو مخزون مركزي من صور الحاويات، يمكن رفع صورنا إليه وسحبها منه عندما نحتاجها من أي مكان وفي أي وقت، وذلك بعدة طرق. إما محليًّا، أو عبر عنقود Kubernetes، أو عبر خطوط أنابيب CI/CD، أو غير ذلك.

لنبدأ الآن خطوات رفع push الصورة react-app إلى السجل.

لرفع صورنا إلى السجل ينبغي امتلاك سجل خاص أو أن نستخدم سجلًا عامًا مثل Docker Hub أو خدمة أخرى نحو سجل الحاويات لمنصة ديجيتال أوشن كما فعلنا في هذا المثال، والذي يمكننا استخدامه بالمجان للمشاريع الصغيرة؛ إذ يمكننا حجز مستودع واحد بمساحة تخزينية لا تتجاو 500 ميجا بايت، وهذا يناسب في الواقع يناسب الصورة الوحيدة التي سنرفعها إلى السجل.

يمكننا البدء إذًا بإعداد سجل الحاويات المجاني free-tier registry، واختيار أحد مواقع مراكز البيانات في المنطقة، مع تسمية السجل باسم مناسب مثل my-container-registry، كما هو الحال في الصورة التالية:

img02 docker registry

تنويه: لا بد من توفر حساب خاص على منصة ديجيتال أوشن لاستخدام سجل الحاويات، ويمكن الاسترشاد بدليل فتح الحساب لمزيدٍ من المعلومات عن هذه الخدمة.

سنحتاج الآن لمفتاح الواجهة البرمجية API للاتصال بالسجل، لذا سنتوجه إلى تبويب tokens من الصفحة الرئيسية لحسابنا على المنصة وننشئ مفتاحًا جديدًا خاصًا بنا كما هو الحال في الصورة التالية:

img03 api token doker registry

سنملأ الآن النموذج الظاهر أمامنا بعد الضغط على زر إنشاء رمز جديد Generate New Token ونفَعِّل خيار التحكم الكامل Full Access طالما أنه لن يكون هناك أكثر من مستخدم وحيد للرمز، إذ لا توجد أي مخاطر أمنية.

img04 gnerate token

سينشأ الآن الرمز الجديد، وما علينا سوى نسخه والاحتفاظ به في مكانٍ آمن؛ إذ سنحتاجه لاحقًا عند تسجيل الدخول إلى السجل.

img05 new token

والآن يمكننا تسجيل الدخول إلى سجل الحاويات باستخدام بيانات حسابنا، وذلك عبر كتابة الأمر التالي في الطرفية terminal:

docker login registry.digitalocean.com

سيُطلب منا عندها إدخال اسم المستخدم وكلمة المرور:

OutputUsername: ravish.foo@bar.com
Password:
Login Succeeded

وبإدخالهما تكتمل عملية تسجيل الدخول ويمكن رفع صورة تطبيقنا إلى السجل؛ ويجري ذلك بخطوتين، أولاهما عبر وسم tag الصورة بوسم خاص للرفع كما يلي:

docker tag react-app:v1.0 registry.digitalocean.com/my-container-registry/react-app

وثانيهما رفع الصورة إلى السجل كما يلي:

docker push registry.digitalocean.com/my-container-registry/react-app

وسنحصل على خرج يشبه التالي يؤكد لنا نجاح العملية:

OutputUsing default tag: latest
The push refers to repository [registry.digitalocean.com/my-container-registry/react-app]
5f70bf18a086: Pushed
fafde3127bc5: Pushed
155c640ab606: Pushed
993afd9f06ad: Pushed
9fd54926bcae: Pushed
175aa66db4cc: Pushed
e6380a7057a5: Pushed
1db2242fc1fa: Pushed
b09347a1aec6: Pushed
bbde741e108b: Pushed
52ec5a4316fa: Pushed
latest: digest: sha256:227a8c3ede3fc5c81b281228600bec84939f8a4a0cc770fc6e5527b3261e77f4 size: 2608

بهذا تكون صورة التطبيق أصبحت قد صارت محفوظةً الآن ضمن سجل الحاويات الخاص بنا، وهي متاحة للسحب، وسنراها مباشرةً من لوحة تحكم حسابنا:

img06 container registry

يمكننا ملاحظة أن الصورة react-app التي رفعناها إلى السجل تحمل الوسم الافتراضي latest لأننا لم نعطها وسمًا خاصًا عند الرفع، فأي صورة تُرفع للسجل دون وسم تُمنح افتراضيًا الوسم latest، ويمكن بالتأكيد تغيير هذا الوسم إلى أي وسم آخر تريده بواسطة خاصية الإصدار Versioning.

فعلى سبيل المثال، يمكن وسم صورة الإصدار الأول من تطبيقنا بالوسم v1.0 وعند إجراء تغيرات لاحقة على شيفرة التطبيق نستطيع إعطاء الصورة الجديدة الوسم v1.1 ثم v1.4 أو v2.0، وهكذا. فننشئ بذلك سجلًا زمنيًا خاصًا بإصدارات تطبيقنا يساعدنا على توثيق تطور التطبيق ويُسَهِّل علينا تتبع إصداراته وإدارتها وخصوصًا على المدى الطويل.

بهذا نكون انتهينا من بناء صورة التطبيق وأصبحت جاهزة على سجل الحاويات، كل ما علينا الآن تشغيل خادم Droplet لنشر deploy الحاوية باستخدام هذه الصورة.

تشغيل خادم Droplet

تنويه: لقد استخدمنا في هذا المثال خادمًا من ديجيتال أوشن لكن يمكن اختيار الاستضافة المناسبة لتأمين الخادم، كما يمكن الاستعانة بمقال التهيئة الأولية لخادم أوبونتو لتجهيزه.

عمليًّا، ستكفينا الخطة ذات 6$ لبناء خادم Droplet مناسب لنشر الحاوية، وسيساعدنا هذا الدليل على إتمام العملية؛ إذ سنختار في البداية نظام التشغيل وليكن أوبنتو، وعند اكتمال تشغيل الخادم سيظهر أمامنا في لوحة تحكم حسابنا كما في الصورة التالية:

img07 droplet server

ننقر على الخادم لرؤية تفاصيله، وننسخ عنوان IP الخاص به، والذي نجده تحت الاسم الخادم كما في الصورة التالية:

img08 droplet ip

يمكن الآن فتح جلسة اتصال ssh مع الخادم كما يلي، وذلك باستخدام عنوان IP المخصص له وكلمة المرور لحساب الجذر root التي تُطلب منا أثناء إعداد الخادم:

ssh root@143.244.130.234
root@143.244.130.234's password:

يمكن أيضًا اختيار عدم المطالبة بكلمة مرور عند الاتصال مع الخادم ببروتوكول ssh إذا أردنا ذلك.

ستظهر أمامنا رسالة الترحيب التالية بمجرد نجاح الاتصال:

Welcome to Ubuntu 23.10 (GNU/Linux 6.5.0-9-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

  System information as of Mon Apr 29 08:07:53 UTC 2024

  System load:  0.32              Processes:             106
  Usage of /:   9.3% of 23.17GB   Users logged in:       0
  Memory usage: 40%               IPv4 address for eth0: 143.244.130.234
  Swap usage:   0%                IPv4 address for eth0: 10.47.0.5

47 updates can be applied immediately.
To see these additional updates run: apt list --upgradable

*** System restart required ***
Last login: Mon Apr 29 08:07:55 2024 from 198.211.111.194
root@ubuntu-s-1vcpu-1gb-blr1-01:~#

وبهذا صرنا متصلين بالخادم ويمكننا تنفيذ التعليمات عليه.

سحب صورة التطبيق من سجل الحاويات

من المهم في البداية التأكد من وجود Docker ضمن الخادم، وذلك بكتابة الأمر التالي:

root@ubuntu-s-1vcpu-1gb-blr1-01:~# docker --version
Docker version 24.0.5, build ced0996

وإذا لم يكن موجودًا يمكننا تثبيته باستخدام الأمر التالي:

root@ubuntu-s-1vcpu-1gb-blr1-01:~# snap install docker

ثم نسجل الدخول إلى سجل الحاوية بالخطوات نفسها التي اتبعناها قبل قليل:

root@ubuntu-s-1vcpu-1gb-blr1-01:~# docker login registry.digitalocean.com
Username: ravish.foo@bar.com
Password:
Login Succeeded

بعد ذلك نستطيع سحب pull صورة التطبيق من السجل كما يلي:

root@ubuntu-s-1vcpu-1gb-blr1-01:~# docker pull registry.digitalocean.com/my-container-registry/react-app

وسنحصل على خرج يشبه التالي:

OutputUsing default tag: latest
latest: Pulling from my-container-registry/react-app
b0a0cf830b12: Already exists
8ddb1e6cdf34: Already exists
5252b206aac2: Already exists
988b92d96970: Already exists
7102627a7a6e: Already exists
93295add984d: Already exists
ebde0aa1d1aa: Already exists
6156e0586e61: Already exists
88bdf71f3911: Pull complete
f5524cce8c8a: Pull complete
4f4fb700ef54: Pull complete
Digest: sha256:227a8c3ede3fc5c81b281228600bec84939f8a4a0cc770fc6e5527b3261e77f4
Status: Downloaded newer image for registry.digitalocean.com/my-container-registry/react-app:latest
registry.digitalocean.com/my-container-registry/react-app:latest

وكما نلاحظ، فالأمر السابق قد سَحَبَ الصورة ذات الوسم latest أي الصورة الأحدث، لأننا لم نُحَدِّد أي وسم آخر في أمر السحب، وفي مثل هذه الحالة يسحب docker الصورة latest تلقائيًا.

تشغيل الحاوية

لدينا الآن صورة عن أحدث إصدار من التطبيق تشمل كل الاعتماديات اللازمة لعمله، ويمكننا تشغيل الحاوية بواسطتها وفق الأمر التالي:

docker run -dp 80:80 registry.digitalocean.com/my-container-registry/react-app

وكتوضيح للشيفرة أعلاه، تجعل الراية d- الحاوية تعمل في الخلفية، وتصل الراية p 80:80 البوابة 80 في الخادم المضيف بالبوابة 80 في الحاوية لتتجه حركة مرور البيانات الواردة للخادم إلى بوابة الحاوية.

البوابة 80 هي البوابة المعيارية لحركة البيانات وفق البروتوكول HTTP وقد ضبطنا الخادم nginx ليستقبل الطلبات عبرها.

سننفِّذ الآن الأمر التالي للتأكد من أن الحاوية قيد التشغيل:

root@ubuntu-s-1vcpu-1gb-blr1-01:~# docker ps -a

يَعرِضُ هذا الأمر الحاويات المُشَغّلة حاليًا على الخادم، سنجد حاويتنا بينها مع عبارة مثل "UP 2 seconds" تبين الزمن الذي أمضته الحاوية في وضع التشغيل. وحالما نتأكد من عمل الحاوية سيكون تطبيقنا متاحًا على الإنترنت للراغبين باستخدامه.

يمكننا تجربة ذلك عبر كتابة عنوان IP لخادم Droplet في متصفحنا وستفتح أمامنا واجهة تطبيق React:

img09 react app web page

يمكننا إيقاف تشغيل الحاوية بواسطة الأمر:

docker stop <container_id>

وإذا أردنا نستطيع حذفها بعد إيقافها بكتابة التالي:

docker rm <container_id>

الخلاصة

يمكن الانطلاق مما تعلمناه هنا وتطوير آلية نشر تطبيقاتنا عبر أتمتة عمليات بناء النسخ ورفعها إلى سجل الحاويات وسحبها منه؛ حيث يمكن مثلًا كتابة سكريبت Bash يتكامل مع GitHub فيبني تلقائيًا صورة جديدة لتطبيقنا ويرفعها إلى سجل الحاويات مع كل عملية إيداع commit تنفذها على التطبيق، كما نستطيع إنشاء خط أنابيب للنشر المستمر والتسليم المستمر CI/CD يؤتمت عملية سحب الصور من السجل ونشرها على السحابة، فضلًا عن إمكانية استخدام عناقيد Kubernetes في عمليات النشر واسعة النطاق، والتي تتيح لنا إمكانية إدارة مجموعة كاملة من الحاويات بدلًا واحدة.

ترجمة -وبتصرف- لمقال How to deploy your React app using Container Registry لصاحبيه Ravish Ahmad Khan و Anish Singh Walia.

اقرأ أيضًا

#3qpa7meed #ArabProgrammers #المبرمجون_العرب #arab_programmers #عقبة_البرق

تعليقات