<?xml version="1.0"?>
<rss version="2.0"><channel><title>DevOps: Docker</title><link>https://academy.hsoub.com/devops/cloud-computing/docker/?d=4</link><description>DevOps: Docker</description><language>ar</language><item><title>&#x625;&#x646;&#x634;&#x627;&#x621; &#x62D;&#x627;&#x648;&#x64A;&#x629; &#x62F;&#x648;&#x643;&#x631; &#x644;&#x62A;&#x637;&#x628;&#x64A;&#x642; &#x631;&#x64A;&#x622;&#x643;&#x62A; &#x648;&#x631;&#x641;&#x639;&#x647;&#x627; &#x625;&#x644;&#x649; &#x633;&#x62C;&#x644; &#x627;&#x644;&#x62D;&#x627;&#x648;&#x64A;&#x627;&#x62A; &#x641;&#x64A; DigitalOcean</title><link>https://academy.hsoub.com/devops/cloud-computing/docker/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%AD%D8%A7%D9%88%D9%8A%D8%A9-%D8%AF%D9%88%D9%83%D8%B1-%D9%84%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B1%D9%8A%D8%A2%D9%83%D8%AA-%D9%88%D8%B1%D9%81%D8%B9%D9%87%D8%A7-%D8%A5%D9%84%D9%89-%D8%B3%D8%AC%D9%84-%D8%A7%D9%84%D8%AD%D8%A7%D9%88%D9%8A%D8%A7%D8%AA-%D9%81%D9%8A-digitalocean-r845/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2025_05/DigitalOcean.png.3d0c52fe8a8f73328885cc7b9d14a0f5.png" /></p>
<p>
	أَحدَثَت مشاريع الحوسبة السحابية الأصيلة Cloud-native Computing تغييرًا كبيرًا في طريقة بناء التطبيقات ونشرها، ووفرت وسائل متنوعة تُسَهِّل العمل، بدايةً من أدوات التكامل Integrating وتحزيم Packaging شيفرة التطبيق تمهيدًا لنشره، ووصولًا إلى أدوات التوسعة Scaling وغير ذلك.
</p>

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

<h2 id="">
	متطلبات العمل
</h2>

<p>
	من أجل إنشاء حاوية دوكر لتطبيق <a href="https://academy.hsoub.com/programming/javascript/react/%D9%85%D8%A7-%D9%87%D9%8A-react%D8%9F-r773/" rel="">ريآكت</a> ورفعها إلى سجل الحاويات في DigitalOcean، سنحتاج إلى المتطلبات التالية:
</p>

<ul>
	<li>
		إنشاء حساب على منصة <a href="https://cloud.digitalocean.com/registrations/new" rel="external nofollow">DigitalOcean</a>.
	</li>
	<li>
		تثبيت كل من <a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D8%AF%D9%88%D9%83%D8%B1-%D9%88%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85%D9%87-%D8%B9%D9%84%D9%89-%D8%AF%D8%A8%D9%8A%D8%A7%D9%86-r465/" rel="">دوكر Docker</a> و<a href="https://academy.hsoub.com/programming/javascript/nodejs/%D8%AF%D9%84%D9%8A%D9%84%D9%83-%D8%A7%D9%84%D8%B4%D8%A7%D9%85%D9%84-%D8%A5%D9%84%D9%89-%D9%85%D8%AF%D9%8A%D8%B1-%D8%A7%D9%84%D8%AD%D8%B2%D9%85-npm-%D9%81%D9%8A-nodejs-r1465/" rel="">مدير الحزم npm</a> على جهازننا
	</li>
	<li>
		تشغيل خادم Droplet على DigitalOcean
	</li>
	<li>
		إعداد <a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%B3%D8%AC%D9%84-%D8%AF%D9%88%D9%83%D8%B1-docker-registry-%D8%AE%D8%A7%D8%B5-%D8%B9%D9%84%D9%89-%D8%AE%D8%A7%D8%AF%D9%85-%D8%A3%D9%88%D8%A8%D9%86%D8%AA%D9%88-2204-r814/" rel="">سجل الحاويات</a> على DigitalOcean
	</li>
</ul>

<h3 id="docker">
	تثبيت Docker
</h3>

<p>
	تساعدنا <a href="https://docs.docker.com/get-docker/" rel="external nofollow">توثيقات دوكر</a> و<a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D8%AF%D9%88%D9%83%D8%B1-%D9%88%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85%D9%87-%D8%B9%D9%84%D9%89-%D8%AF%D8%A8%D9%8A%D8%A7%D9%86-r465/" rel="">هذا الدليل</a> على تثبيت دوكر على الجهاز. وعند إتمام التثبيت، سنستخدم الأمر <code>docker --version</code> للاستعلام عن إصدار دوكر والتحقق من نجاح تثبيته، وذلك وفق التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6962_12" style=""><span class="pln">docker </span><span class="pun">--</span><span class="pln">version
</span><span class="typ">Docker</span><span class="pln"> version </span><span class="lit">26.0</span><span class="pun">.</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> build </span><span class="lit">2ae903e</span></pre>

<h2 id="-1">
	إنشاء تطبيق ريآكت
</h2>

<p>
	خطوتنا التالية هي تشغيل <a href="https://academy.hsoub.com/programming/javascript/react/" rel="">تطبيق ريآكت React</a>، فإذا كان التطبيق الذي نخطط لاستخدامه موجودًا على الجهاز المحلي، فلا بد من تشغيله مباشرةً. وإذا كان موجودًا على <a href="https://academy.hsoub.com/programming/workflow/git/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%B7%D9%84%D8%A8-%D8%B3%D8%AD%D8%A8-%D8%B9%D9%84%D9%89-github-r1581/" rel="">GitHub</a>، فيمكن استنساخ مستودع التطبيق إلى الجهاز مباشرةً؛ أما في حال لم نكن نمتلك تطبيقًا، فلا بد من إنشاء واحد باستخدام <a href="https://vitejs.dev/" rel="external nofollow">Vite</a> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6962_10" style=""><span class="pln">npm create vite@latest react</span><span class="pun">-</span><span class="pln">app </span><span class="pun">--</span><span class="pln"> </span><span class="pun">--</span><span class="pln">template react</span></pre>

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

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6962_16" style=""><span class="pln">cd react</span><span class="pun">-</span><span class="pln">app
npm install
npm run dev</span></pre>

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

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6962_18" style=""><span class="typ">OutputVITE</span><span class="pln"> v5</span><span class="pun">.</span><span class="lit">2.11</span><span class="pln">  ready in </span><span class="lit">712</span><span class="pln"> ms
</span><span class="pun">➜</span><span class="pln">  </span><span class="typ">Local</span><span class="pun">:</span><span class="pln">   http</span><span class="pun">:</span><span class="com">//localhost:5173/</span><span class="pln">
</span><span class="pun">➜</span><span class="pln">  </span><span class="typ">Network</span><span class="pun">:</span><span class="pln"> use </span><span class="pun">--</span><span class="pln">host to expose
</span><span class="pun">➜</span><span class="pln">  press h </span><span class="pun">+</span><span class="pln"> enter to show help</span></pre>

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

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="172396" href="https://academy.hsoub.com/uploads/monthly_2025_05/img01-vite-react-app.png.056d19484085845db300f3e07c9b3461.png" rel=""><img alt="img01 vite react app" class="ipsImage ipsImage_thumbnailed" data-fileid="172396" data-unique="014kp20g4" style="width: 750px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2025_05/img01-vite-react-app.thumb.png.4377e109ad786cbe69d2d70c82280d2e.png"> </a>
</p>

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

<h2 id="dockerfile">
	إنشاء الملف Dockerfile
</h2>

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

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

<p>
	تُخَزَّن هذه الطبقات بهيئة قيم مُعَمَّاة hash من نوع <code>SHA-256</code>، ويمكن معرفة المزيد عن بنية صور الحاويات بمطالعة مقال <a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D9%85%D8%A7-%D9%87%D9%8A-%D8%B5%D9%88%D8%B1%D8%A9-%D8%A7%D9%84%D8%AD%D8%A7%D9%88%D9%8A%D8%A9-container-image%D8%9F-r587/" rel="">ما هي صورة الحاوية container image؟</a>
</p>

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

<ul>
	<li>
		بناء التطبيق
	</li>
	<li>
		إعداد خادم <code>nginx</code> وتشغيله لخدمة التطبيق
	</li>
</ul>

<p>
	<strong>تنويه</strong>: ستُكتَب جميع التعليمات التي ننفذها في هاتين الخطوتين ضمن الملف <code>Dockerfile</code>.
</p>

<h3 id="1">
	1. بناء التطبيق
</h3>

<p>
	سنوجه Docker في البداية ليستخدم أحدث نسخة من <a href="https://academy.hsoub.com/programming/javascript/nodejs/" rel="">Node.js</a> لتكون الركيزة التي سيبني عليها تطبيق React وفق التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6962_20" style=""><span class="pln">FROM node</span><span class="pun">:</span><span class="pln">latest AS builder</span></pre>

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

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6962_22" style=""><span class="pln">WORKDIR </span><span class="pun">/</span><span class="pln">app</span></pre>

<p>
	سننسخ بعدها الملف <code>package.json</code> إلى الحاوية <code>builder</code> ونشَغِّل <code>npm install</code> لتثبيت جميع الاعتماديات المطلوبة في الملف <code>package.json</code> وبذلك نضمن أن <a href="https://academy.hsoub.com/programming/javascript/react/%D8%A8%D9%86%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-react-%D8%A8%D8%A7%D8%B3%D8%AA%D8%B9%D9%85%D8%A7%D9%84-%D8%AE%D8%A7%D8%AF%D9%85-graphql-r1210/" rel="">تطبيق React</a> يمتلك كل ما يحتاجه ليعمل بصورة سليمة:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6962_26" style=""><span class="pln">COPY package</span><span class="pun">.</span><span class="pln">json </span><span class="pun">.</span><span class="pln">
RUN npm install</span></pre>

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

<pre class="ipsCode">COPY . ./
RUN npm run build
</pre>

<h3 id="2nginx">
	2. إعداد خادم <code>nginx</code> للتطبيق وتشغيله
</h3>

<p>
	يشتهر <a href="https://academy.hsoub.com/devops/servers/web/nginx/" rel="">NGINX</a> بالكفاءة والسرعة، لذا يُعدّ خيارًا مناسبًا للاستخدام كخادم ويب لتطبيقنا حتى يصله بالمستخدمين، وسنعتمد لبنائه أحدث صورة <code>nginx</code> متوفرة؛ ثم نستبدل ملف الإعدادات الافتراضي الذي يأتي مع الصورة <code>default.conf</code> بملف إعدادات مشروعنا ليتمكن خادم <code>nginx</code> من تلبية طلبات المستخدمين الواردة إلى التطبيق.
</p>

<p>
	وذلك وفق الأوامر التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6962_28" style=""><span class="pln">FROM nginx</span><span class="pun">:</span><span class="pln">latest
RUN rm </span><span class="pun">-</span><span class="pln">rf </span><span class="pun">/</span><span class="pln">etc</span><span class="pun">/</span><span class="pln">nginx</span><span class="pun">/</span><span class="pln">conf</span><span class="pun">.</span><span class="pln">d</span><span class="pun">/</span><span class="kwd">default</span><span class="pun">.</span><span class="pln">conf
COPY </span><span class="pun">./</span><span class="pln">nginx</span><span class="pun">/</span><span class="kwd">default</span><span class="pun">.</span><span class="pln">conf </span><span class="pun">/</span><span class="pln">etc</span><span class="pun">/</span><span class="pln">nginx</span><span class="pun">/</span><span class="pln">conf</span><span class="pun">.</span><span class="pln">d</span><span class="pun">/</span><span class="kwd">default</span><span class="pun">.</span><span class="pln">conf</span></pre>

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

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6962_30" style=""><span class="pln">COPY </span><span class="pun">--</span><span class="pln">from</span><span class="pun">=</span><span class="pln">builder </span><span class="pun">/</span><span class="pln">app</span><span class="pun">/</span><span class="pln">dist </span><span class="pun">/</span><span class="pln">usr</span><span class="pun">/</span><span class="pln">share</span><span class="pun">/</span><span class="pln">nginx</span><span class="pun">/</span><span class="pln">html</span></pre>

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

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6962_32" style=""><span class="pln">WORKDIR </span><span class="pun">/</span><span class="pln">usr</span><span class="pun">/</span><span class="pln">share</span><span class="pun">/</span><span class="pln">nginx</span><span class="pun">/</span><span class="pln">html</span></pre>

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

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6962_35" style=""><span class="pln">CMD </span><span class="pun">[</span><span class="str">"/bin/bash"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"-c"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"nginx -g "</span><span class="pln">daemon off</span><span class="pun">;</span><span class="str">""</span><span class="pun">]</span></pre>

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

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6962_37" style=""><span class="pun">#</span><span class="pln"> </span><span class="pun">------------------------</span><span class="pln">
</span><span class="pun">#</span><span class="pln"> </span><span class="typ">Step</span><span class="pln"> </span><span class="lit">1</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Build</span><span class="pln"> react app
</span><span class="pun">#</span><span class="pln"> </span><span class="pun">------------------------</span><span class="pln">

</span><span class="pun">#</span><span class="pln"> </span><span class="typ">Use</span><span class="pln"> node</span><span class="pun">:</span><span class="pln">latest as the builder image
FROM node</span><span class="pun">:</span><span class="pln">latest AS builder

</span><span class="pun">#</span><span class="pln"> </span><span class="typ">Set</span><span class="pln"> the working directory
WORKDIR </span><span class="pun">/</span><span class="pln">app

</span><span class="pun">#</span><span class="pln"> </span><span class="typ">Copy</span><span class="pln"> package</span><span class="pun">.</span><span class="pln">json and install app dependencies
COPY package</span><span class="pun">.</span><span class="pln">json </span><span class="pun">.</span><span class="pln">
RUN npm install

</span><span class="pun">#</span><span class="pln"> </span><span class="typ">Copy</span><span class="pln"> other project files and build
COPY </span><span class="pun">.</span><span class="pln"> </span><span class="pun">./</span><span class="pln">
RUN npm run build

</span><span class="pun">#</span><span class="pln"> </span><span class="pun">--------------------------------------</span><span class="pln">
</span><span class="pun">#</span><span class="pln"> </span><span class="typ">Step</span><span class="pln"> </span><span class="lit">2</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Set</span><span class="pln"> up nginx to serve the app
</span><span class="pun">#</span><span class="pln"> </span><span class="pun">--------------------------------------</span><span class="pln">
</span><span class="pun">#</span><span class="pln"> </span><span class="typ">Use</span><span class="pln"> nginx</span><span class="pun">:</span><span class="pln">latest as the base image
FROM nginx</span><span class="pun">:</span><span class="pln">latest

</span><span class="pun">#</span><span class="pln"> </span><span class="typ">Overwriting</span><span class="pln"> nginx config </span><span class="kwd">with</span><span class="pln"> our own config file
RUN rm </span><span class="pun">-</span><span class="pln">rf </span><span class="pun">/</span><span class="pln">etc</span><span class="pun">/</span><span class="pln">nginx</span><span class="pun">/</span><span class="pln">conf</span><span class="pun">.</span><span class="pln">d</span><span class="pun">/</span><span class="kwd">default</span><span class="pun">.</span><span class="pln">conf
COPY </span><span class="pun">./</span><span class="pln">nginx</span><span class="pun">/</span><span class="kwd">default</span><span class="pun">.</span><span class="pln">conf </span><span class="pun">/</span><span class="pln">etc</span><span class="pun">/</span><span class="pln">nginx</span><span class="pun">/</span><span class="pln">conf</span><span class="pun">.</span><span class="pln">d</span><span class="pun">/</span><span class="kwd">default</span><span class="pun">.</span><span class="pln">conf

</span><span class="pun">#</span><span class="pln"> </span><span class="typ">Copy</span><span class="pln"> over the build created in the </span><span class="typ">Step</span><span class="pln"> </span><span class="lit">1</span><span class="pln">
COPY </span><span class="pun">--</span><span class="pln">from</span><span class="pun">=</span><span class="pln">builder </span><span class="pun">/</span><span class="pln">app</span><span class="pun">/</span><span class="pln">dist </span><span class="pun">/</span><span class="pln">usr</span><span class="pun">/</span><span class="pln">share</span><span class="pun">/</span><span class="pln">nginx</span><span class="pun">/</span><span class="pln">html

</span><span class="pun">#</span><span class="pln"> </span><span class="typ">Set</span><span class="pln"> the working directory
WORKDIR </span><span class="pun">/</span><span class="pln">usr</span><span class="pun">/</span><span class="pln">share</span><span class="pun">/</span><span class="pln">nginx</span><span class="pun">/</span><span class="pln">html

</span><span class="pun">#</span><span class="pln"> </span><span class="typ">Start</span><span class="pln"> nginx server
CMD </span><span class="pun">[</span><span class="str">"/bin/bash"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"-c"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"nginx -g \"daemon off;\""</span><span class="pun">]</span></pre>

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

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6962_39" style=""><span class="pln">touch </span><span class="typ">Dockerfile</span></pre>

<p>
	مع نسخ التعليمات السابقة ضمنه.
</p>

<h2 id="nginx">
	إنشاء ملف إعدادات NGINX
</h2>

<p>
	ذكرنا في الخطوة السابقة الخاصة بإعداد <a href="https://academy.hsoub.com/devops/servers/web/nginx/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-nginx-%D8%B9%D9%84%D9%89-%D8%A3%D9%88%D8%A8%D9%88%D9%86%D8%AA%D9%88-1804-r434/" rel="">خادم NGINX</a> أننا سنستبدل ملف الإعدادات الافتراضي للخادم بملف إعدادات مشروعنا، فكيف ننشئ ملف إعدادات المشروع؟
</p>

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

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6962_41" style=""><span class="pln">mkdir nginx
cd nginx </span><span class="pun">&amp;&amp;</span><span class="pln"> touch </span><span class="kwd">default</span><span class="pun">.</span><span class="pln">conf</span></pre>

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

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6962_43" style=""><span class="pln">server </span><span class="pun">{</span><span class="pln">
  listen </span><span class="lit">80</span><span class="pun">;</span><span class="pln">
  add_header </span><span class="typ">Cache</span><span class="pun">-</span><span class="typ">Control</span><span class="pln"> no</span><span class="pun">-</span><span class="pln">cache</span><span class="pun">;</span><span class="pln">
  location </span><span class="pun">/</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    root   </span><span class="pun">/</span><span class="pln">usr</span><span class="pun">/</span><span class="pln">share</span><span class="pun">/</span><span class="pln">nginx</span><span class="pun">/</span><span class="pln">html</span><span class="pun">;</span><span class="pln">
    index  index</span><span class="pun">.</span><span class="pln">html index</span><span class="pun">.</span><span class="pln">htm</span><span class="pun">;</span><span class="pln">
    try_files $uri $uri</span><span class="pun">/</span><span class="pln"> </span><span class="pun">/</span><span class="pln">index</span><span class="pun">.</span><span class="pln">html</span><span class="pun">;</span><span class="pln">
    expires </span><span class="pun">-</span><span class="lit">1</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
  error_page   </span><span class="lit">500</span><span class="pln"> </span><span class="lit">502</span><span class="pln"> </span><span class="lit">503</span><span class="pln"> </span><span class="lit">504</span><span class="pln">  </span><span class="pun">/</span><span class="lit">50x</span><span class="pun">.</span><span class="pln">html</span><span class="pun">;</span><span class="pln">
  location </span><span class="pun">=</span><span class="pln"> </span><span class="pun">/</span><span class="lit">50x</span><span class="pun">.</span><span class="pln">html </span><span class="pun">{</span><span class="pln">
    root   </span><span class="pun">/</span><span class="pln">usr</span><span class="pun">/</span><span class="pln">share</span><span class="pun">/</span><span class="pln">nginx</span><span class="pun">/</span><span class="pln">html</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	تُحَدِّد هذه الإعدادات البوابة التي سيستقبل خادم <code>nginx</code> الطلبات عبرها، وهي البوابة <code>80</code>، وأيضًا الملفات التي سيتعامل معها، بالإضافة إلى الأخطاء الافتراضية التي قد تعترضه مصنفةً حسب رمز الخطأ. وفي هذا المجال، يُنصح بالاطلاع على مقال <a href="https://academy.hsoub.com/devops/servers/web/nginx/%D9%81%D9%87%D9%85-%D8%A8%D9%86%D9%8A%D8%A9-%D9%85%D9%84%D9%81-%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF%D8%A7%D8%AA-nginx-%D9%88%D8%B3%D9%8A%D8%A7%D9%82%D8%A7%D8%AA-%D8%A7%D9%84%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF%D8%A7%D8%AA-r112/" rel="">فهم بنية ملف إعدادات nginx وسياقات الإعدادات</a>، كما توفر DigitalOcean لمستخدميها <a href="https://www.digitalocean.com/community/tools/nginx?domains.0.php.php=false&amp;domains.0.routing.index=index.html&amp;domains.0.routing.fallbackHtml=true" rel="external nofollow">أداة رسومية</a> تساعدهم في إنشاء ملف إعدادات nginx.
</p>

<h2 id="image">
	بناء صورة التطبيق image
</h2>

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

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6962_45" style=""><span class="pln">docker image build </span><span class="pun">-</span><span class="pln">t react</span><span class="pun">-</span><span class="pln">app</span><span class="pun">:</span><span class="pln">v1</span><span class="pun">.</span><span class="lit">0</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">--</span><span class="pln">platform linux</span><span class="pun">/</span><span class="pln">amd64</span></pre>

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

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

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6962_47" style=""><span class="pun">[+]</span><span class="pln"> </span><span class="typ">Building</span><span class="pln"> </span><span class="lit">113.9s</span><span class="pln"> </span><span class="pun">(</span><span class="lit">17</span><span class="pun">/</span><span class="lit">17</span><span class="pun">)</span><span class="pln"> FINISHED
</span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">[</span><span class="pln">internal</span><span class="pun">]</span><span class="pln"> load build definition from </span><span class="typ">Dockerfile</span><span class="pln">
</span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> transferring dockerfile</span><span class="pun">:</span><span class="pln"> </span><span class="lit">982B</span><span class="pln">
</span><span class="pun">...</span><span class="pln">
</span><span class="pun">...</span><span class="pln">
</span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> writing image sha256</span><span class="pun">:</span><span class="pln">d125b102b094224c82ddb69f9ece98c7161c8660fe72fd5aec57e41a6d72cf2f
</span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> naming to docker</span><span class="pun">.</span><span class="pln">io</span><span class="pun">/</span><span class="pln">library</span><span class="pun">/</span><span class="pln">react</span><span class="pun">-</span><span class="pln">app</span><span class="pun">:</span><span class="pln">v1</span><span class="pun">.</span><span class="lit">0</span></pre>

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

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6962_49" style=""><span class="pln">docker image list</span></pre>

<p>
	وسنحصل على النتيجة التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6962_51" style=""><span class="typ">OutputREPOSITORY</span><span class="pln">   TAG   IMAGE ID       CREATED         SIZE
react</span><span class="pun">-</span><span class="pln">app    v1</span><span class="pun">.</span><span class="lit">0</span><span class="pln">  d125b102b094   </span><span class="lit">6</span><span class="pln"> minutes ago   </span><span class="lit">188MB</span></pre>

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

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

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

<h2 id="-2">
	رفع صورة التطبيق إلى سجل الحاويات
</h2>

<p>
	سجل الحاويات Container Registry هو مخزون مركزي من صور الحاويات، يمكن رفع صورنا إليه وسحبها منه عندما نحتاجها من أي مكان وفي أي وقت، وذلك بعدة طرق. إما محليًّا، أو عبر عنقود <a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D9%85%D8%A7-%D8%A7%D9%84%D9%81%D8%B1%D9%82-%D8%A8%D9%8A%D9%86-%D8%AF%D9%88%D9%83%D8%B1-docker-%D9%88%D9%83%D9%88%D8%A8%D9%8A%D8%B1%D9%86%D9%8A%D8%AA%D9%8A%D8%B3-kubernetes%D8%9F-r612/" rel="">Kubernetes</a>، أو عبر خطوط أنابيب <a href="https://academy.hsoub.com/devops/deployment/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D8%AA%D9%83%D8%A7%D9%85%D9%84-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D9%85%D8%B1-%D9%88%D8%A7%D9%84%D9%86%D8%B4%D8%B1-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D9%85%D8%B1-cicd-r644/" rel="">CI/CD</a>، أو غير ذلك.
</p>

<p>
	<iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="" frameborder="0" height="603" id="ips_uid_7517_5" referrerpolicy="strict-origin-when-cross-origin" src="https://academy.hsoub.com/applications/core/interface/index.html" title="ما هو التكامل المستمر والنشر المستمر CI/CD" width="930" data-embed-src="https://www.youtube.com/embed/hFzSG9qNWWs"></iframe>
</p>

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

<p>
	لرفع صورنا إلى السجل ينبغي امتلاك سجل خاص أو أن نستخدم سجلًا عامًا مثل Docker Hub أو خدمة أخرى نحو <a href="https://www.digitalocean.com/products/container-registry" rel="external nofollow">سجل الحاويات لمنصة ديجيتال أوشن</a> كما فعلنا في هذا المثال، والذي يمكننا استخدامه بالمجان للمشاريع الصغيرة؛ إذ يمكننا حجز مستودع واحد بمساحة تخزينية لا تتجاو 500 ميجا بايت، وهذا يناسب في الواقع يناسب الصورة الوحيدة التي سنرفعها إلى السجل.
</p>

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

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="172391" href="https://academy.hsoub.com/uploads/monthly_2025_05/img02-docker-registry.png.a3924d6850528298710f0b9156f9c3ae.png" rel=""><img alt="img02 docker registry" class="ipsImage ipsImage_thumbnailed" data-fileid="172391" data-unique="7sg7omq6v" style="width: 750px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2025_05/img02-docker-registry.thumb.png.b79a88ebf37d59b16f0f6625850c7142.png"> </a>
</p>

<p>
	<strong>تنويه</strong>: لا بد من توفر حساب خاص على منصة ديجيتال أوشن لاستخدام سجل الحاويات، ويمكن الاسترشاد <a href="https://docs.digitalocean.com/products/container-registry/getting-started/quickstart/#create-a-registry" rel="external nofollow">بدليل فتح الحساب</a> لمزيدٍ من المعلومات عن هذه الخدمة.
</p>

<p>
	سنحتاج الآن لمفتاح <a href="https://academy.hsoub.com/programming/general/%D9%85%D8%A7-%D9%87%D9%8A-%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-%D9%84%D9%84%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-api%D8%9F-r1512/" rel="">الواجهة البرمجية <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr></a> للاتصال بالسجل، لذا سنتوجه إلى تبويب <strong>tokens</strong> من الصفحة الرئيسية لحسابنا على المنصة وننشئ مفتاحًا جديدًا خاصًا بنا كما هو الحال في الصورة التالية:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="172395" href="https://academy.hsoub.com/uploads/monthly_2025_05/img03-api-token-doker-registry.png.cd4fa337aace24307ce3964717c29133.png" rel=""><img alt="img03 api token doker registry" class="ipsImage ipsImage_thumbnailed" data-fileid="172395" data-unique="rssxb6in9" style="width: 750px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2025_05/img03-api-token-doker-registry.thumb.png.08c26d36a2b369e7ff3f9dff780f91da.png"> </a>
</p>

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

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="172397" href="https://academy.hsoub.com/uploads/monthly_2025_05/img04-gnerate-token.png.5939a3bf2589362741f6194c10a5e739.png" rel=""><img alt="img04 gnerate token" class="ipsImage ipsImage_thumbnailed" data-fileid="172397" data-unique="6g5lnigqz" style="width: 750px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2025_05/img04-gnerate-token.thumb.png.c84d12f53dcc9d245d7d134acdb06195.png"> </a>
</p>

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

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="172394" href="https://academy.hsoub.com/uploads/monthly_2025_05/img05-new-token.png.40e456cc622c62f70e7751a35345413a.png" rel=""><img alt="img05 new token" class="ipsImage ipsImage_thumbnailed" data-fileid="172394" data-unique="gca3rmgfi" style="width: 750px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2025_05/img05-new-token.thumb.png.e4cca9c7bb9a7c7a793df8ed9d6a6bf5.png"> </a>
</p>

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

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6962_54" style=""><span class="pln">docker login registry</span><span class="pun">.</span><span class="pln">digitalocean</span><span class="pun">.</span><span class="pln">com</span></pre>

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

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6962_56" style=""><span class="typ">OutputUsername</span><span class="pun">:</span><span class="pln"> ravish</span><span class="pun">.</span><span class="pln">foo@bar</span><span class="pun">.</span><span class="pln">com
</span><span class="typ">Password</span><span class="pun">:</span><span class="pln">
</span><span class="typ">Login</span><span class="pln"> </span><span class="typ">Succeeded</span></pre>

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

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6962_58" style=""><span class="pln">docker tag react</span><span class="pun">-</span><span class="pln">app</span><span class="pun">:</span><span class="pln">v1</span><span class="pun">.</span><span class="lit">0</span><span class="pln"> registry</span><span class="pun">.</span><span class="pln">digitalocean</span><span class="pun">.</span><span class="pln">com</span><span class="pun">/</span><span class="pln">my</span><span class="pun">-</span><span class="pln">container</span><span class="pun">-</span><span class="pln">registry</span><span class="pun">/</span><span class="pln">react</span><span class="pun">-</span><span class="pln">app</span></pre>

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

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6962_62" style=""><span class="pln">docker push registry</span><span class="pun">.</span><span class="pln">digitalocean</span><span class="pun">.</span><span class="pln">com</span><span class="pun">/</span><span class="pln">my</span><span class="pun">-</span><span class="pln">container</span><span class="pun">-</span><span class="pln">registry</span><span class="pun">/</span><span class="pln">react</span><span class="pun">-</span><span class="pln">app</span></pre>

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

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6962_60" style=""><span class="typ">OutputUsing</span><span class="pln"> </span><span class="kwd">default</span><span class="pln"> tag</span><span class="pun">:</span><span class="pln"> latest
</span><span class="typ">The</span><span class="pln"> push refers to repository </span><span class="pun">[</span><span class="pln">registry</span><span class="pun">.</span><span class="pln">digitalocean</span><span class="pun">.</span><span class="pln">com</span><span class="pun">/</span><span class="pln">my</span><span class="pun">-</span><span class="pln">container</span><span class="pun">-</span><span class="pln">registry</span><span class="pun">/</span><span class="pln">react</span><span class="pun">-</span><span class="pln">app</span><span class="pun">]</span><span class="pln">
</span><span class="lit">5f70bf18a086</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Pushed</span><span class="pln">
fafde3127bc5</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Pushed</span><span class="pln">
</span><span class="lit">155c640ab606</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Pushed</span><span class="pln">
</span><span class="lit">993afd9f06ad</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Pushed</span><span class="pln">
</span><span class="lit">9fd54926bcae</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Pushed</span><span class="pln">
</span><span class="lit">175aa66db4cc</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Pushed</span><span class="pln">
e6380a7057a5</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Pushed</span><span class="pln">
</span><span class="lit">1db2242fc1fa</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Pushed</span><span class="pln">
b09347a1aec6</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Pushed</span><span class="pln">
bbde741e108b</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Pushed</span><span class="pln">
</span><span class="lit">52ec5a4316fa</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Pushed</span><span class="pln">
latest</span><span class="pun">:</span><span class="pln"> digest</span><span class="pun">:</span><span class="pln"> sha256</span><span class="pun">:</span><span class="lit">227a8c3ede3fc5c81b281228600bec84939f8a4a0cc770fc6e5527b3261e77f4</span><span class="pln"> size</span><span class="pun">:</span><span class="pln"> </span><span class="lit">2608</span></pre>

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

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="172389" href="https://academy.hsoub.com/uploads/monthly_2025_05/img06-container-registry.png.5bbc0df42f163c99e3df6919327a7e9c.png" rel=""><img alt="img06 container registry" class="ipsImage ipsImage_thumbnailed" data-fileid="172389" data-unique="nitqw64d8" style="width: 750px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2025_05/img06-container-registry.thumb.png.6ac284860b6fc56e638b90afc3b702d5.png"> </a>
</p>

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

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

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

<h2 id="droplet">
	تشغيل خادم Droplet
</h2>

<p>
	<strong>تنويه</strong>: لقد استخدمنا في هذا المثال خادمًا من ديجيتال أوشن لكن يمكن اختيار الاستضافة المناسبة لتأمين الخادم، كما يمكن الاستعانة بمقال <a href="https://academy.hsoub.com/devops/linux/%D8%A7%D9%84%D8%AA%D9%87%D9%8A%D8%A6%D8%A9-%D8%A7%D9%84%D8%A3%D9%88%D9%84%D9%8A%D8%A9-%D9%84%D8%AE%D8%A7%D8%AF%D9%85-%D8%A3%D9%88%D8%A8%D9%88%D9%86%D8%AA%D9%88-1804-r431/" rel="">التهيئة الأولية لخادم أوبونتو</a> لتجهيزه.
</p>

<p>
	عمليًّا، ستكفينا الخطة ذات 6$ لبناء خادم Droplet مناسب لنشر الحاوية، وسيساعدنا <a href="https://docs.digitalocean.com/products/droplets/how-to/create/" rel="external nofollow">هذا الدليل</a> على إتمام العملية؛ إذ سنختار في البداية نظام التشغيل وليكن <a href="https://academy.hsoub.com/tags/%D8%A3%D9%88%D8%A8%D9%86%D8%AA%D9%88/" rel="">أوبنتو</a>، وعند اكتمال تشغيل الخادم سيظهر أمامنا في لوحة تحكم حسابنا كما في الصورة التالية:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="172392" href="https://academy.hsoub.com/uploads/monthly_2025_05/img07-droplet-server.png.b66564e8f0925abbee60c1ef50b3b4b6.png" rel=""><img alt="img07 droplet server" class="ipsImage ipsImage_thumbnailed" data-fileid="172392" data-unique="n62c43tmq" style="width: 750px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2025_05/img07-droplet-server.thumb.png.047f2e6c2f929e2c034040f6cd825598.png"> </a>
</p>

<p>
	ننقر على الخادم لرؤية تفاصيله، وننسخ <a href="https://academy.hsoub.com/devops/networking/%D8%B4%D8%A8%D9%83%D8%A9-%D8%A7%D9%84%D8%A5%D9%86%D8%AA%D8%B1%D9%86%D8%AA-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A8%D8%B1%D9%88%D8%AA%D9%88%D9%83%D9%88%D9%84-ip-r497/" rel="">عنوان IP</a> الخاص به، والذي نجده تحت الاسم الخادم كما في الصورة التالية:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="172390" href="https://academy.hsoub.com/uploads/monthly_2025_05/img08-droplet-ip.png.0c2c7715ad2c355d5bae37625ba453c9.png" rel=""><img alt="img08 droplet ip" class="ipsImage ipsImage_thumbnailed" data-fileid="172390" data-unique="22itfn8tf" style="width: 750px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2025_05/img08-droplet-ip.thumb.png.d479c3aba2def1e6b50dc78f979bc93c.png"> </a>
</p>

<p>
	يمكن الآن فتح <a href="https://academy.hsoub.com/devops/security/ssh/" rel="">جلسة اتصال <abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة"><abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة">ssh</abbr></abbr></a> مع الخادم كما يلي، وذلك باستخدام عنوان IP المخصص له وكلمة المرور لحساب الجذر <code>root</code> التي تُطلب منا أثناء إعداد الخادم:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6962_64" style=""><span class="pln">ssh root@143</span><span class="pun">.</span><span class="lit">244.130</span><span class="pun">.</span><span class="lit">234</span><span class="pln">
root@143</span><span class="pun">.</span><span class="lit">244.130</span><span class="pun">.</span><span class="lit">234</span><span class="str">'s password:</span></pre>

<p>
	يمكن أيضًا اختيار عدم المطالبة بكلمة مرور عند الاتصال مع الخادم ببروتوكول <code><abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة"><a href="https://academy.hsoub.com/devops/security/ssh/%D9%85%D8%A7-%D9%87%D9%8A-%D8%AA%D9%82%D9%86%D9%8A%D8%A9-ssh%D8%9F-r793/" rel=""><abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة">ssh</abbr></a></abbr></code> إذا أردنا ذلك.
</p>

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

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6962_66" style=""><span class="typ">Welcome</span><span class="pln"> to </span><span class="typ">Ubuntu</span><span class="pln"> </span><span class="lit">23.10</span><span class="pln"> </span><span class="pun">(</span><span class="pln">GNU</span><span class="pun">/</span><span class="typ">Linux</span><span class="pln"> </span><span class="lit">6.5</span><span class="pun">.</span><span class="lit">0</span><span class="pun">-</span><span class="lit">9</span><span class="pun">-</span><span class="pln">generic x86_64</span><span class="pun">)</span><span class="pln">

 </span><span class="pun">*</span><span class="pln"> </span><span class="typ">Documentation</span><span class="pun">:</span><span class="pln">  https</span><span class="pun">:</span><span class="com">//help.ubuntu.com</span><span class="pln">
 </span><span class="pun">*</span><span class="pln"> </span><span class="typ">Management</span><span class="pun">:</span><span class="pln">     https</span><span class="pun">:</span><span class="com">//landscape.canonical.com</span><span class="pln">
 </span><span class="pun">*</span><span class="pln"> </span><span class="typ">Support</span><span class="pun">:</span><span class="pln">        https</span><span class="pun">:</span><span class="com">//ubuntu.com/advantage</span><span class="pln">

  </span><span class="typ">System</span><span class="pln"> information as </span><span class="kwd">of</span><span class="pln"> </span><span class="typ">Mon</span><span class="pln"> </span><span class="typ">Apr</span><span class="pln"> </span><span class="lit">29</span><span class="pln"> </span><span class="lit">08</span><span class="pun">:</span><span class="lit">07</span><span class="pun">:</span><span class="lit">53</span><span class="pln"> UTC </span><span class="lit">2024</span><span class="pln">

  </span><span class="typ">System</span><span class="pln"> load</span><span class="pun">:</span><span class="pln">  </span><span class="lit">0.32</span><span class="pln">              </span><span class="typ">Processes</span><span class="pun">:</span><span class="pln">             </span><span class="lit">106</span><span class="pln">
  </span><span class="typ">Usage</span><span class="pln"> </span><span class="kwd">of</span><span class="pln"> </span><span class="pun">/:</span><span class="pln">   </span><span class="lit">9.3</span><span class="pun">%</span><span class="pln"> </span><span class="kwd">of</span><span class="pln"> </span><span class="lit">23.17GB</span><span class="pln">   </span><span class="typ">Users</span><span class="pln"> logged in</span><span class="pun">:</span><span class="pln">       </span><span class="lit">0</span><span class="pln">
  </span><span class="typ">Memory</span><span class="pln"> usage</span><span class="pun">:</span><span class="pln"> </span><span class="lit">40</span><span class="pun">%</span><span class="pln">               </span><span class="typ">IPv4</span><span class="pln"> address </span><span class="kwd">for</span><span class="pln"> eth0</span><span class="pun">:</span><span class="pln"> </span><span class="lit">143.244</span><span class="pun">.</span><span class="lit">130.234</span><span class="pln">
  </span><span class="typ">Swap</span><span class="pln"> usage</span><span class="pun">:</span><span class="pln">   </span><span class="lit">0</span><span class="pun">%</span><span class="pln">                </span><span class="typ">IPv4</span><span class="pln"> address </span><span class="kwd">for</span><span class="pln"> eth0</span><span class="pun">:</span><span class="pln"> </span><span class="lit">10.47</span><span class="pun">.</span><span class="lit">0.5</span><span class="pln">

</span><span class="lit">47</span><span class="pln"> updates can be applied immediately</span><span class="pun">.</span><span class="pln">
</span><span class="typ">To</span><span class="pln"> see these additional updates run</span><span class="pun">:</span><span class="pln"> apt list </span><span class="pun">--</span><span class="pln">upgradable

</span><span class="pun">***</span><span class="pln"> </span><span class="typ">System</span><span class="pln"> restart required </span><span class="pun">***</span><span class="pln">
</span><span class="typ">Last</span><span class="pln"> login</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Mon</span><span class="pln"> </span><span class="typ">Apr</span><span class="pln"> </span><span class="lit">29</span><span class="pln"> </span><span class="lit">08</span><span class="pun">:</span><span class="lit">07</span><span class="pun">:</span><span class="lit">55</span><span class="pln"> </span><span class="lit">2024</span><span class="pln"> from </span><span class="lit">198.211</span><span class="pun">.</span><span class="lit">111.194</span><span class="pln">
root@ubuntu</span><span class="pun">-</span><span class="pln">s</span><span class="pun">-</span><span class="lit">1vcpu</span><span class="pun">-</span><span class="lit">1gb</span><span class="pun">-</span><span class="pln">blr1</span><span class="pun">-</span><span class="lit">01</span><span class="pun">:~#</span></pre>

<p>
	وبهذا صرنا متصلين <a href="https://academy.hsoub.com/devops/servers/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%AE%D8%A7%D8%AF%D9%85-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-r574/" rel="">بالخادم</a> ويمكننا تنفيذ التعليمات عليه.
</p>

<h2 id="-3">
	سحب صورة التطبيق من سجل الحاويات
</h2>

<p>
	من المهم في البداية التأكد من وجود <a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-docker-r3/" rel="">Docker</a> ضمن الخادم، وذلك بكتابة الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6962_68" style=""><span class="pln">root@ubuntu</span><span class="pun">-</span><span class="pln">s</span><span class="pun">-</span><span class="lit">1vcpu</span><span class="pun">-</span><span class="lit">1gb</span><span class="pun">-</span><span class="pln">blr1</span><span class="pun">-</span><span class="lit">01</span><span class="pun">:~#</span><span class="pln"> docker </span><span class="pun">--</span><span class="pln">version
</span><span class="typ">Docker</span><span class="pln"> version </span><span class="lit">24.0</span><span class="pun">.</span><span class="lit">5</span><span class="pun">,</span><span class="pln"> build ced0996</span></pre>

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

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6962_70" style=""><span class="pln">root@ubuntu</span><span class="pun">-</span><span class="pln">s</span><span class="pun">-</span><span class="lit">1vcpu</span><span class="pun">-</span><span class="lit">1gb</span><span class="pun">-</span><span class="pln">blr1</span><span class="pun">-</span><span class="lit">01</span><span class="pun">:~#</span><span class="pln"> snap install docker</span></pre>

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

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6962_72" style=""><span class="pln">root@ubuntu</span><span class="pun">-</span><span class="pln">s</span><span class="pun">-</span><span class="lit">1vcpu</span><span class="pun">-</span><span class="lit">1gb</span><span class="pun">-</span><span class="pln">blr1</span><span class="pun">-</span><span class="lit">01</span><span class="pun">:~#</span><span class="pln"> docker login registry</span><span class="pun">.</span><span class="pln">digitalocean</span><span class="pun">.</span><span class="pln">com
</span><span class="typ">Username</span><span class="pun">:</span><span class="pln"> ravish</span><span class="pun">.</span><span class="pln">foo@bar</span><span class="pun">.</span><span class="pln">com
</span><span class="typ">Password</span><span class="pun">:</span><span class="pln">
</span><span class="typ">Login</span><span class="pln"> </span><span class="typ">Succeeded</span></pre>

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

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6962_74" style=""><span class="pln">root@ubuntu</span><span class="pun">-</span><span class="pln">s</span><span class="pun">-</span><span class="lit">1vcpu</span><span class="pun">-</span><span class="lit">1gb</span><span class="pun">-</span><span class="pln">blr1</span><span class="pun">-</span><span class="lit">01</span><span class="pun">:~#</span><span class="pln"> docker pull registry</span><span class="pun">.</span><span class="pln">digitalocean</span><span class="pun">.</span><span class="pln">com</span><span class="pun">/</span><span class="pln">my</span><span class="pun">-</span><span class="pln">container</span><span class="pun">-</span><span class="pln">registry</span><span class="pun">/</span><span class="pln">react</span><span class="pun">-</span><span class="pln">app</span></pre>

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

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6962_76" style=""><span class="typ">OutputUsing</span><span class="pln"> </span><span class="kwd">default</span><span class="pln"> tag</span><span class="pun">:</span><span class="pln"> latest
latest</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Pulling</span><span class="pln"> from my</span><span class="pun">-</span><span class="pln">container</span><span class="pun">-</span><span class="pln">registry</span><span class="pun">/</span><span class="pln">react</span><span class="pun">-</span><span class="pln">app
b0a0cf830b12</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Already</span><span class="pln"> exists
</span><span class="lit">8ddb1e6cdf34</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Already</span><span class="pln"> exists
</span><span class="lit">5252b206aac2</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Already</span><span class="pln"> exists
</span><span class="lit">988b92d96970</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Already</span><span class="pln"> exists
</span><span class="lit">7102627a7a6e</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Already</span><span class="pln"> exists
</span><span class="lit">93295add984d</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Already</span><span class="pln"> exists
ebde0aa1d1aa</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Already</span><span class="pln"> exists
</span><span class="lit">6156e0586e61</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Already</span><span class="pln"> exists
</span><span class="lit">88bdf71f3911</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Pull</span><span class="pln"> complete
f5524cce8c8a</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Pull</span><span class="pln"> complete
</span><span class="lit">4f4fb700ef54</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Pull</span><span class="pln"> complete
</span><span class="typ">Digest</span><span class="pun">:</span><span class="pln"> sha256</span><span class="pun">:</span><span class="lit">227a8c3ede3fc5c81b281228600bec84939f8a4a0cc770fc6e5527b3261e77f4</span><span class="pln">
</span><span class="typ">Status</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Downloaded</span><span class="pln"> newer image </span><span class="kwd">for</span><span class="pln"> registry</span><span class="pun">.</span><span class="pln">digitalocean</span><span class="pun">.</span><span class="pln">com</span><span class="pun">/</span><span class="pln">my</span><span class="pun">-</span><span class="pln">container</span><span class="pun">-</span><span class="pln">registry</span><span class="pun">/</span><span class="pln">react</span><span class="pun">-</span><span class="pln">app</span><span class="pun">:</span><span class="pln">latest
registry</span><span class="pun">.</span><span class="pln">digitalocean</span><span class="pun">.</span><span class="pln">com</span><span class="pun">/</span><span class="pln">my</span><span class="pun">-</span><span class="pln">container</span><span class="pun">-</span><span class="pln">registry</span><span class="pun">/</span><span class="pln">react</span><span class="pun">-</span><span class="pln">app</span><span class="pun">:</span><span class="pln">latest</span></pre>

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

<h2 id="-4">
	تشغيل الحاوية
</h2>

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

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6962_78" style=""><span class="pln">docker run </span><span class="pun">-</span><span class="pln">dp </span><span class="lit">80</span><span class="pun">:</span><span class="lit">80</span><span class="pln"> registry</span><span class="pun">.</span><span class="pln">digitalocean</span><span class="pun">.</span><span class="pln">com</span><span class="pun">/</span><span class="pln">my</span><span class="pun">-</span><span class="pln">container</span><span class="pun">-</span><span class="pln">registry</span><span class="pun">/</span><span class="pln">react</span><span class="pun">-</span><span class="pln">app</span></pre>

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

<p>
	البوابة <code>80</code> هي البوابة المعيارية لحركة البيانات وفق <a href="https://academy.hsoub.com/devops/servers/web/" rel="">البروتوكول HTTP</a> وقد ضبطنا الخادم <code>nginx</code> ليستقبل الطلبات عبرها.
</p>

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

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6962_80" style=""><span class="pln">root@ubuntu</span><span class="pun">-</span><span class="pln">s</span><span class="pun">-</span><span class="lit">1vcpu</span><span class="pun">-</span><span class="lit">1gb</span><span class="pun">-</span><span class="pln">blr1</span><span class="pun">-</span><span class="lit">01</span><span class="pun">:~#</span><span class="pln"> docker ps </span><span class="pun">-</span><span class="pln">a</span></pre>

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

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

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="172393" href="https://academy.hsoub.com/uploads/monthly_2025_05/img09-react-app-web-page.png.2179e5051db27031191db98e0665bfc9.png" rel=""><img alt="img09 react app web page" class="ipsImage ipsImage_thumbnailed" data-fileid="172393" data-unique="9lul1ua14" style="width: 750px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2025_05/img09-react-app-web-page.thumb.png.e9e6d72de7bdef5cc32ee58a4c067a6b.png"> </a>
</p>

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

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6962_82" style=""><span class="pln">docker stop </span><span class="pun">&lt;</span><span class="pln">container_id</span><span class="pun">&gt;</span></pre>

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

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6962_84" style=""><span class="pln">docker rm </span><span class="pun">&lt;</span><span class="pln">container_id</span><span class="pun">&gt;</span></pre>

<h2 id="-5">
	الخلاصة
</h2>

<p>
	يمكن الانطلاق مما تعلمناه هنا وتطوير آلية نشر تطبيقاتنا عبر أتمتة عمليات بناء النسخ ورفعها إلى سجل الحاويات وسحبها منه؛ حيث يمكن مثلًا كتابة <a href="https://academy.hsoub.com/devops/linux/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%B5%D8%AF%D9%81%D8%A9-%D8%A8%D8%A7%D8%B4-bash-r606/" rel="">سكريبت Bash</a> يتكامل مع GitHub فيبني تلقائيًا صورة جديدة لتطبيقنا ويرفعها إلى سجل الحاويات مع كل عملية إيداع commit تنفذها على التطبيق، كما نستطيع إنشاء خط أنابيب <a href="https://academy.hsoub.com/devops/deployment/%D8%A3%D9%81%D8%B6%D9%84-%D9%85%D9%85%D8%A7%D8%B1%D8%B3%D8%A7%D8%AA-%D9%85%D9%86%D9%87%D8%AC-%D8%A7%D9%84%D8%AA%D9%83%D8%A7%D9%85%D9%84-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D9%85%D8%B1-%D9%88%D8%A7%D9%84%D8%AA%D8%B3%D9%84%D9%8A%D9%85-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D9%85%D8%B1-cicd-r687/" rel="">للنشر المستمر والتسليم المستمر CI/CD</a> يؤتمت عملية سحب الصور من السجل ونشرها على السحابة، فضلًا عن إمكانية استخدام عناقيد <a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D9%85%D8%A7-%D8%A7%D9%84%D9%81%D8%B1%D9%82-%D8%A8%D9%8A%D9%86-%D8%AF%D9%88%D9%83%D8%B1-docker-%D9%88%D9%83%D9%88%D8%A8%D9%8A%D8%B1%D9%86%D9%8A%D8%AA%D9%8A%D8%B3-kubernetes%D8%9F-r612/" rel="">Kubernetes</a> في عمليات النشر واسعة النطاق، والتي تتيح لنا إمكانية إدارة مجموعة كاملة من الحاويات بدلًا واحدة.
</p>

<p>
	ترجمة -وبتصرف- لمقال <a href="https://www.digitalocean.com/community/developer-center/how-to-deploy-your-react-app-using-container-registry" rel="external nofollow">How to deploy your React app using Container Registry</a> لصاحبيه Ravish Ahmad Khan و Anish Singh Walia.
</p>

<h2 id="-6">
	اقرأ أيضًا
</h2>

<ul>
	<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D8%AD%D8%A7%D9%88%D9%8A%D8%A7%D8%AA-r641/" rel="">مدخل إلى الحاويات</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%AF%D9%88%D9%83%D8%B1-docker-r607/" rel="">مدخل إلى دوكر Docker</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D8%AD%D8%A7%D9%88%D9%8A%D8%A7%D8%AA-docker-r310/" rel="">التعامل مع حاويات Docker</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D8%AA%D9%86%D8%B3%D9%8A%D9%82-%D8%A7%D9%84%D8%AD%D8%A7%D9%88%D9%8A%D8%A7%D8%AA-r643/" rel="">أساسيات تنسيق الحاويات</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">845</guid><pubDate>Mon, 23 Jun 2025 16:00:02 +0000</pubDate></item><item><title>&#x625;&#x639;&#x627;&#x62F;&#x629; &#x627;&#x644;&#x62A;&#x634;&#x63A;&#x64A;&#x644; &#x627;&#x644;&#x62A;&#x644;&#x642;&#x627;&#x626;&#x64A; &#x644;&#x62D;&#x627;&#x648;&#x64A;&#x627;&#x62A; &#x62F;&#x648;&#x643;&#x631;</title><link>https://academy.hsoub.com/devops/cloud-computing/docker/%D8%A5%D8%B9%D8%A7%D8%AF%D8%A9-%D8%A7%D9%84%D8%AA%D8%B4%D8%BA%D9%8A%D9%84-%D8%A7%D9%84%D8%AA%D9%84%D9%82%D8%A7%D8%A6%D9%8A-%D9%84%D8%AD%D8%A7%D9%88%D9%8A%D8%A7%D8%AA-%D8%AF%D9%88%D9%83%D8%B1-r839/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2025_03/1170758675_.png.e431e4926be02b4bedadd36d94de31f3.png" /></p>
<p>
	عند إعادة تشغيل الخادم سنحتاج لإعادة تشغيل حاويات دوكر Docker من أجل ضمان استمرار تشغيل الخدمات داخل هذه الحاويات وتجنب توقفها. في هذا المقال سنوضح ثلاثة طرق مختلفة لبدء تشغيل حاويات دوكر تلقائيًا بعد إعادة تشغيل النظام، حيث ستعتمد الطريقة الأولى والثانية على وجود خدمة دوكر على مستوى الجهاز أي أنها تتطلب وجود عملية dockerd تعمل في الخلفية وتدير الحاويات وإلا سنواجه أخطاء في تطبيقها.
</p>

<p>
	لنشرح الطرق الثلاثة بالتفصيل في الفقرات التالية:
</p>

<h2 id="restartalways">
	الطريقة الأولى: استخدام خيار <code>--restart always</code>
</h2>

<p>
	هذه الطريقة هي الإعداد الافتراضي لتشغيل <a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D8%AD%D8%A7%D9%88%D9%8A%D8%A7%D8%AA-docker-r310/" rel="">الحاوية</a>، فعندما نشغل حاوية دوكر باستخدام الأمر <code>docker run</code> يمكننا تحديد الخيار <code>restart always--</code> لنقرر تشغيل الحاوية تلقائيًا عند إعادة تشغيل الخادم، أو عند توقف الحاوية لأي سبب كان.
</p>

<p>
	يوضح المثال التالي تشغيل حاوية أباتشي مع تفعيل إعادة التشغيل التلقائي:
</p>

<pre class="ipsCode">docker run -p 8080:80 --name apache -v "${PWD}":/usr/local/apache2/htdocs -d --restart always httpd
</pre>

<p>
	هنالك عدة احتمالات ممكنة للخيار <code>restart--</code> نتحكم من خلالها في كيفية إعادة تشغيل الحاوية تلقائيًا تشمل التالي:
</p>

<ul>
	<li>
		<strong><code>restart no--</code></strong> وهو الإعداد الافتراضي، ويعني عدم تشغيل الحاوية تلقائيًا عند توقف الحاوية لأي سبب كان
	</li>
	<li>
		<strong><code>restart always--</code></strong> إعادة تشغيل الحاوية تلقائيًا بمجرد توقفها، وسيعاد تشغيل جميع الحاويات التي بدأ تشغيلها سابقًا باستخدام هذا الخيار<br>
		<strong>تحذير</strong>: إذا كانت الحاوية تحتوي على خطأ برمجي تسبب في توقفها، فيتسبب هذا الخيار في محاولة إعادة تشغيلها بصورة مستمرة ومتكررة، إلا لو أوقفناها يدويًا باستخدام الأمر <code>docker stop</code> فعندها سيتوقف تشغيلها في نفس الجلسة، لكن ستعاد محاولة تشغيلها من جديد عند إعادة تشغيل الخادم
	</li>
	<li>
		<strong><code>restart unless-stopped--</code></strong> يعمل هذا الخيار بشكل مشابه لخيار <code>restart always--</code> الفرق هنا هو عدم إعادة تشغيل الحاوية التي أوقفناها يدويًا باستخدام <code>docker stop</code>
	</li>
	<li>
		<strong><code>restart on-failure--</code></strong> إعادة تشغيل الحاوية تلقائيًا فقط في حالة توقفها بسبب خطأ ما
	</li>
</ul>

<p>
	يمكننا معرفة سلوك إعادة التشغيل حاوية باستخدام الأمر <code>docker inspect</code>:
</p>

<pre class="ipsCode">docker inspect &lt;containername&gt;
  ...
  "RestartPolicy": {
      "Name": "always",
      "MaximumRetryCount": 0
  },
  ...
</pre>

<p>
	لتغيير سلوك إعادة تشغيل الحاوية أثناء تشغيلها بالفعل، يمكننا استخدام الأمر <code>docker update</code>، على سبيل المثال، إذا كنا نحتاج لتغيير أسلوب إعادة تشغيل حاوية معينة ونريد إعادة تشغيلها فقط في حالة حدوث خطأ في الحاوية سنكتب الأمر التالي:
</p>

<pre class="ipsCode">docker update --restart on-failure &lt;containername&gt;
</pre>

<h2 id="dockercompose">
	الطريقة الثانية: استخدام Docker Compose
</h2>

<p>
	يمكننا أيضًا تحديد طريقة إعادة التشغيل التلقائي للحاوية من خلال <a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D8%A7%D9%84%D8%A3%D8%AF%D8%A7%D8%A9-%D8%AF%D9%88%D9%83%D8%B1-%D9%83%D9%88%D9%85%D8%A8%D9%88%D8%B2-docker-compose-%D9%88%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85%D9%87%D8%A7-%D8%B6%D9%85%D9%86-%D9%86%D8%B8%D8%A7%D9%85-%D9%84%D9%8A%D9%86%D9%83%D8%B3-%D8%A3%D9%88%D8%A8%D9%88%D9%86%D8%AA%D9%88-r654/" rel="">ملف docker-compose.yml</a> باستخدام الكلمة المفتاحية restart كما يلي:
</p>

<pre class="ipsCode">services:
   db:
     image: mariadb:latest
     restart: always
</pre>

<p>
	هناك أربعة إعدادات مسموح بها هنا وهي:
</p>

<ul>
	<li>
		no وهو الخيار الافتراضي ويعني إعادة تشغيل الحاوية يدويًا فقط
	</li>
	<li>
		always لإعادة تشغيل الحاوية دائمًا
	</li>
	<li>
		unless-stopped لإعادة تشغيل الحاوية في كافة الأحوال، إلا في حال أوقفناها يدويًا
	</li>
	<li>
		on-failure لإعادة تشغيل الحاوية فقط في حال توقفت نتيجة لحدوث خطأ
	</li>
</ul>

<p>
	تتشابه دلالة الخيارات السابقة مع الخيارات المشروحة في الطريقة الأولى التي تستخدم الخيار <code>restart--</code> مع الأمر <code>docker run</code>، لكن هنا سيكون المعامل <code>restart: always</code> ساري المفعول حتى نوقف الحاويات الموجودة في الملف <code>docker-compose.yml</code> ونحذفها يدويًا باستخدام الأمر <code>docker-compose down</code>.
</p>

<p>
	<strong>ملاحظة</strong>: ينبغي التأكد من تحديد الكلمة المفتاحية <code>restart</code> في المستوى الصحيح داخل إعدادات الحاوية المعنية وهي <code>db</code> في المثال.
</p>

<pre class="ipsCode">services:
  db:
    image: mariadb:latest
    volumes:
      - vol-db:/var/lib/mysql
    environment:
      MYSQL_USER: wpuser
      MYSQL_PASSWORD: password
    restart: always
</pre>

<h2 id="systemd">
	الطريقة الثالثة: إعادة تشغيل الحاوية باستخدام systemd
</h2>

<p>
	الطريقة الأساسية لبدء تشغيل حاويات Docker هي عن باستخدام الأمر <code>docker run</code> والذي يسمح لنا بإنشاء حاوية من صورة دوكر Docker image وتشغيلها، لكن هناك مشكلة في هذه الطريقة ففي حال حدوث أي مشكلة تتسبب في توقف الخادم أو إعادة تشغيله فستتوقف الحاوية التي شغلناها عبر هذا الأمر ولن يعاد تشغيلها تلقائيًا، مما يؤدي إلى توقف الخدمة و التطبيق الذي توفره.
</p>

<p>
	يمكننا حل هذه المشكلة وضمان استمرار عمل الحاوية بتحويل الأمر <code>docker run</code> إلى خدمة نظام لضمان مواصلة عملها بعد إعادة تشغيل الخادم، فالحاوية في هذه الحالة ستكون جزءًا من خدمة وبالتالي يمكن تشغيلها تلقائيًا عند إقلاع الخادم، وإعادة تشغيلها تلقائيًا إذا توقفت لأي سبب كان.
</p>

<p>
	يمكن تحقيق ذلك باستخدام مدير الخدمات systemd، كما يمكننا استخدام supervisord أيضًا أو أي نظام مشابه آخر لإدارة الخدمات، ولكننا اخترنا systemd في مقالنا الحالي لكونه الأكثر استخدمًا وهو معتمد في معظم توزيعات لينكس.
</p>

<p>
	سنغلف حاوية دوكر في خدمة systemd، كي نتمكن من إدارة هذه الحاوية بنفس الطريقة التي ندير بها التطبيقات والخدمات الأخرى على جهازنا، وضمان الأمور التالية:
</p>

<ul>
	<li>
		إمكانية بدء الخدمة بشكل صحيح عند تشغيل الجهاز
	</li>
	<li>
		إمكانية إعادة تشغيل الحاوية تلقائيًا في حال توقفت لأي سبب كان باستخدام إعداد <code>Restart=always</code> في ملف خدمة systemd
	</li>
	<li>
		سهولة مراجعة وإدارة سجلات الحاوية stdout بواسطة نظام تسجيل السجلات Journald
	</li>
	<li>
		استخدام كامل نظام systemd لإدارة المتطلبات كالتبعيات والأوامر التي يجب أن تنفذ قبل وبعد تشغيل الحاوية وتعريف متغيرات البيئة التي تحتاجها الحاوية بكفاءة
	</li>
</ul>

<p>
	يعرض الكود التالي الحد الأدنى لمحتوى ملف خدمة systemd لتشغيل حاوية دوكر تحتوي على خادم nginx والموجود في المسار <code>/etc/systemd/system/nginx.service</code>:
</p>

<pre class="ipsCode">[Unit]
Description=Docker container

[Service]
ExecStart=/usr/bin/docker run --name nginx \
    --net host \
    -v /srv/nginx/conf.d:/etc/nginx/conf.d \
    -v /srv/nginx/index.html:/usr/share/nginx/html/index.html \
    nginx

ExecStop=/usr/bin/docker stop nginx
ExecStopPost=/usr/bin/docker rm -f nginx

[Install]
WantedBy=multi-user.target
</pre>

<p>
	يحدد هذا الملف الأمور التالية:
</p>

<ul>
	<li>
		تشغيل الحاوية التي تحتوي على خادم nginx تلقائيًا عند تشغيل النظام
	</li>
	<li>
		إيقاف الحاوية بشكل صحيح عند إيقاف الخدمة
	</li>
	<li>
		حذف الحاوية بعد إيقافها
	</li>
	<li>
		تشغيل الخدمة تلقائيًا عند تشغيل النظام ووصوله لمرحلة multi-user target
	</li>
</ul>

<p>
	لكن هذه الطريقة لها بعض السلبيات، ونحتاج لإجراء بعض التعديلات على ملف الخدمة لضمان عمله بشكل صحيح، فهنا بدأنا تشغيل حاوية Docker باسم nginx، وسحبنا أحدث صورة <a href="https://hub.docker.com/_/nginx/" rel="external nofollow"><code>nginx</code></a> من <a href="https://hub.docker.com/_/nginx" rel="external nofollow">المستودع الرسمي</a> مع تمرير مجلد الإعدادات على شكل volume داخل الحاوية،وفي حال لم يكن موجودًا ستنشئه حاوية دوكر على الخادم.
</p>

<p>
	سيعمل جزء <code>WantedBy</code> فقط عند تنفيذ الأمر <code>systemctl enable nginx</code>، وفي هذه الحالة ستفعَّل الخدمة عند بدء التشغيل لأنها ستكون مرتبطة بالوصول لمرحلة multi-user target في systemd، هذا يعني أنه عند تشغيل النظام، ستفعّل الخدمة تلقائيًا.
</p>

<p>
	الحل هو بإضافة الأسطر <code>BindsTo</code> و <code>After</code> في ملف الخدمة لضمان أن الخدمة تعتمد على دوكر. بمعنى آخر، إذا توقفت خدمة دوكر أو لم تكن تعمل بعد، فإن هذه الخدمة لن تبدأ بالعمل إلا بعد أن نضمن تشغيل دوكر بنجاح، وسيجري إيقافها أولًا إذا توقفت حاوية دوكر في أي وقت.
</p>

<p>
	أيضًا، في حالتنا nginx التي هي خدمة عديمة الحالة stateless أي أنها لا تحفظ حالتها بين عمليات التشغيل المختلفة، لذا سيكون من الأفضل تحديث صورة دوكر الخاصة بالخدمة لأحدث إصدار متاح كلما شغلنا هذه الخدمة لنضمن بأنها تعمل بأحدث نسخة من الحاوية، ولتحقيق سنضيف أمر <code>ExecStartPre</code> المسؤول عن تنفيذ <code>docker pull</code> كي يعمل قبل الأمر <code>ExecStart</code>.
</p>

<p>
	كما يمكننا فرض إعادة تشغيل الخدمة دائمًا في حال تعرضت لخطأ، وذلك كل 10 ثوانٍ مثلًا، من خلال الأسطر <code>Restart</code> و <code>RestartSec</code> والتي تشابه في تأثيرها الخيار <code>restart=on-failure</code> في أمر <code>docker run</code> الذي شرحناه في الطريقة الأولى.
</p>

<p>
	أخيرًا هناك مشكلة أخرى سنواجهها في حال استخدمنا systemd لإدارة الحاويات. فعند تشغيل خدمة حاوية دوكر عبر systemd، سنحتاج لتحديد سلوك إعادة التشغيل يدويًا عند إعادة تشغيل الخدمة أو توقفها لأن systemd يفترض بأننا نرغب في الاحتفاظ بالأسلوب المستخدم لإعادة التشغيل، حيث سيوقف الخدمة أولاً ثم يعيد تشغيلها مرة أخرى.
</p>

<p>
	لحل هذه المشكلة يمكن استخدام حل بسيط وهو استخدام الأمر <code>ExecReload</code> لإعادة تحميل عملية nginx داخل الحاوية فهذا يساعدنا عند الحاجة لتحديث إعدادات nginx أو إعادة تحميلها دون توقف الخدمة بالكامل، كما يمكننا استخدام حل بديل وهو إرسال إشارة باستخدام الأمر <code>kill -s HUP</code>، لنخبر nginx بأن يعيد تحميل إعداداته دون إيقافه بالكامل.
</p>

<p>
	أخيرًا، يمكننا تغيير الأسماء المستخدمة في ملف الخدمة باستخدام متغيرات البيئة في السطر <code>Environment</code>. وفي حال وجدنا أن المتغيرات أصبحت كثيرة أو كانت مشتركة بين عدة ملفات خدمة على نفس الخادم، فيمكن استخدام ملف منفصل للمتغيرات باستخدام الخيار <code>EnvironmentFile</code>.
</p>

<p>
	فيما يلي ملف الخدمة بعد إجراء التحسينات السابقة:
</p>

<pre class="ipsCode">[Unit]
Description=Docker container
BindsTo=docker.service
After=docker.service

[Service]
Environment=NAME=%N
Environment=IMG=nginx
Restart=on-failure
RestartSec=10
ExecStartPre=-/usr/bin/docker kill ${NAME}
ExecStartPre=-/usr/bin/docker rm ${NAME}
ExecStart=/usr/bin/docker run --name ${NAME} \
    -p 80:80 \
    -p 443:443 \
    -v /srv/nginx/conf.d:/etc/nginx/conf.d \
    -v /srv/nginx/html:/usr/share/nginx/html/ \
    ${IMG}
ExecStop=/usr/bin/docker stop ${NAME}
ExecReload=/usr/bin/docker exec ${NAME} nginx -s reload

[Install]
WantedBy=multi-user.target
</pre>

<p>
	<strong>ملاحظة</strong>: يجري استبدال <code>N%</code> تلقائيًا باسم ملف الخدمة، وعلينا أن لا ننسى تشغيل الأمر <code>systemctl daemon-reload</code> في كل مرة نغير فيها إعدادات ملف الخدمة.
</p>

<h3 id="-1">
	مراقبة سجلات الخدمة
</h3>

<p>
	عند تشغيل الخدمات باستخدام systemd مثل خدمة nginx في المثال السابق، ستوجه السجلات التي تنتج عن الخدمة إلى سجلات النظام <span ipsnoautolink="true">journald</span>، وهذا يسهل علينا الوصول لها وتحليلها.
</p>

<p>
	على سبيل المثال، يمكننا الوصول إلى سجلات nginx ببساطة لمراقبتها وعرضها في الزمن الحقيقي من خلال الأمر التالي:
</p>

<pre class="ipsCode">journalctl -fu nginx
</pre>

<p>
	لا يمنعنا هذا الأسلوب من الحصول على ملفات سجلات منفصلة، فقد نحتاج لذلك كما في حالة استخدام virtualhosts لإدارة عدة مواقع ويب على نفس الخادم باستخدام nginx، في هذه الحالة سيكون من الأنسب لنا مشاركة مجلد السجلات الخاص بالحاوية وهو بشكل افتراضي <code>/var/log/nginx</code> مع جهاز المضيف حتى لا نفقدها عند إعادة التشغيل.
</p>

<h2 id="-2">
	الخاتمة
</h2>

<p>
	تعرفنا في هذا المقال على ثلاث طرق مختلفة لإعادة تشغيل حاويات دوكر تلقائيًا بعد إعادة تشغيل الخادم، يعتمد اختيار الطريقة المناسبة على البئية ومتطلبات العمل، ففي الحالات البسيطة وإعدادات الحاوية الواحدة قد تكون طريقة <code>restart--</code> في أمر <code>docker run</code> أو طريقة Docker Compose كافية. لكن في البيئات المعقدة التي تتضمن عدة خدمات ستوفر لنا طريقة systemd تحكمًا أكبر بالحاوية.
</p>

<p>
	ترجمة -وبتصرف- للمقال <a href="https://linuxhandbook.com/restart-docker-container-automatically/" rel="external nofollow">Restart Docker Container Automatically After Reboot</a> لكاتبه <a href="https://linuxhandbook.com/author/umair/" rel="external nofollow">Umair Khurshid</a>
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
	<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D8%AB%D9%84%D8%A7%D8%AB-%D9%86%D8%B5%D8%A7%D8%A6%D8%AD-%D9%84%D8%AA%D8%B3%D9%85%D9%8A%D8%A9-%D8%AD%D8%A7%D9%88%D9%8A%D8%A7%D8%AA-docker-r311/" rel="">ثلاث نصائح لتسمية حاويات Docker</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D8%A3%D9%85%D8%B1-docker-exec-%D9%81%D9%8A-%D8%AD%D8%A7%D9%88%D9%8A%D8%A7%D8%AA-docker-r813/" rel="">استخدام الأمر docker exec في حاويات Docker</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D8%AA%D9%86%D8%B3%D9%8A%D9%82-%D8%A7%D9%84%D8%AD%D8%A7%D9%88%D9%8A%D8%A7%D8%AA-r643/" rel="">أساسيات تنسيق الحاويات</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D8%AD%D8%A7%D9%88%D9%8A%D8%A7%D8%AA-r641/" rel="">مدخل إلى الحاويات</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">839</guid><pubDate>Sat, 15 Mar 2025 12:04:01 +0000</pubDate></item><item><title>&#x625;&#x639;&#x62F;&#x627;&#x62F; &#x633;&#x62C;&#x644; &#x62F;&#x648;&#x643;&#x631; Docker Registry &#x62E;&#x627;&#x635; &#x639;&#x644;&#x649; &#x62E;&#x627;&#x62F;&#x645; &#x623;&#x648;&#x628;&#x646;&#x62A;&#x648; 22.04</title><link>https://academy.hsoub.com/devops/cloud-computing/docker/%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%B3%D8%AC%D9%84-%D8%AF%D9%88%D9%83%D8%B1-docker-registry-%D8%AE%D8%A7%D8%B5-%D8%B9%D9%84%D9%89-%D8%AE%D8%A7%D8%AF%D9%85-%D8%A3%D9%88%D8%A8%D9%86%D8%AA%D9%88-2204-r814/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2024_07/---Docker-Registry-----22_04.png.fbd6212afa298235ccc7ba53f5668c6a.png" /></p>
<p>
	يُعرّف سجل دوكر <span ipsnoautolink="true">Docker Registry</span> بأنه تطبيق يُدّير عمليات تخزين صور حاويات Docker وتسليمها للمطورين، حيث تكون صور الحاويات متاحة أمامهم في سجلٍ مركزي واحد ومُضمّن فيها جميع المكونات الضرورية لعملها، وفي هذا استثمارٌ كبير لوقت المطوّر، إذ تكفل صور دوكر بيئة تشغيل مماثلة لمتطلباته عبر المحاكاة الافتراضية، وبهذا يمكن للمطور سحب الصورة التي يحتاجها من السجل مع كل ما يلزم وتنزيلها بهيئة مضغوطة، بدلًا من تنزيل الاعتماديات والحزم واحدة واحدة وتثبيتها داخل الحاوية في كل مرة، وبالمثل أيضًا يستطيع المطوّر أتمتة عمليات نشر الصور على السجل باستخدام أدوات <a href="https://academy.hsoub.com/devops/deployment/%D9%85%D8%A7-%D9%87%D9%88-%D8%A7%D9%84%D8%AA%D9%83%D8%A7%D9%85%D9%84-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D9%85%D8%B1-%D9%88%D8%A7%D9%84%D9%86%D8%B4%D8%B1-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D9%85%D8%B1-cicd%D8%9F-r792/" rel="">التكامل المستمر (CI)</a> مثل <a href="https://www.travis-ci.com/" rel="external nofollow">TravisCI</a> أو غيرها لتحديث صوره باستمرار في مراحل التطوير والإنتاج.
</p>

<p>
	قد تكون سجلات Docker عامة أو خاصة ولعل <a href="https://hub.docker.com" rel="external nofollow">Docker Hub</a> هو أبرز مثال على سجلات دوكر العامة، فهو مجاني ومتاح للجميع ويمكنك تخصيص صورك حسب احتياجات عملك واستضافتها عليه، وإذا رغبت بمستوى أعلى من السرية والخصوصية فيمكنك استخدام سجل خاص بك فهو الخيار الأفضل للتطبيقات مغلقة المصدر، إذ تتضمن الصور عادةً جميع التعليمات البرمجية اللازمة لعمل التطبيق، ويبقيها السجل الخاص في متناول مجموعة محددة من الأشخاص فقط.
</p>

<p>
	سنشرح لك في هذا المقال كيفية إعداد سجلك الخاص لصور حاويات Docker، وطريقة تأمينه، واستخدام كل من <a href="https://docs.docker.com/compose/" rel="external nofollow">Docker Compose</a> لضبط إعدادات التحكم بتشغيل الحاويات، وخادم <a href="https://academy.hsoub.com/devops/servers/web/nginx/" rel="">Nginx</a> لتوجيه حركة مرور البيانات القادمة من الإنترنت إلى حاوية Docker قيد التشغيل. وستمتلك في النهاية المعرفة الكافية لرفع صورة Docker إلى سجلك الخاص، وسحب الصور بأمان من خادمٍ بعيد.
</p>

<h2 id="">
	متطلبات العمل
</h2>

<p>
	ستحتاج لتوفير المتطلبات التالية لتتابع معنا سير العمل خطوة بخطوة:
</p>

<ul>
	<li>
		<p>
			خادمين مثبت عليهما نظام تشغيل أوبنتو (الإصدار 22.04 )، ويمكنك اتباع <a href="https://academy.hsoub.com/devops/linux/%D8%A7%D9%84%D8%AA%D9%87%D9%8A%D8%A6%D8%A9-%D8%A7%D9%84%D8%A3%D9%88%D9%84%D9%8A%D8%A9-%D9%84%D8%AE%D8%A7%D8%AF%D9%85-%D8%A3%D9%88%D8%A8%D9%88%D9%86%D8%AA%D9%88-1804-r431/" rel="">هذا الدليل</a> لتجهيزهما، حيث يتضمن هذا الدليل طريقة إنشاء مستخدم جديد غير مستخدم الجذر لكنه يتمتع بصلاحيات <code>sudo</code> بالإضافة إلى آلية إعداد جدار الحماية للخادم، احرص على تنفيذ هذه الخطوات إذ سيستضيف أحد الخادمين سجل Docker الخاص بك وسنسميه الخادم المضيف host، وسيكون الخادم الآخر عميلًا client يستخدم السجل.
		</p>
	</li>
	<li>
		<p>
			تثبيت Docker على الخادمين، تساعدك الخطوتان 1 و 2 من مقال <a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D8%AF%D9%88%D9%83%D8%B1-%D9%88%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85%D9%87-%D8%B9%D9%84%D9%89-%D8%AF%D8%A8%D9%8A%D8%A7%D9%86-r465/" rel="">كيفية تثبيت دوكر واستخدامه على دبيان</a>  في تنفيذ المطلوب فتثبيت Docker على دبيان يشبه تثبيته على أوبنتو.
		</p>
	</li>
</ul>

<p>
	اضبط بعد ذلك الإعدادات التالية على الخادم المضيف:
</p>

<ul>
	<li>
		<p>
			ثبّت عليه دوكر كومبوزر Docker Compose مستعينًا بالخطوات الواردة في مقال <a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D8%A7%D9%84%D8%A3%D8%AF%D8%A7%D8%A9-%D8%AF%D9%88%D9%83%D8%B1-%D9%83%D9%88%D9%85%D8%A8%D9%88%D8%B2-docker-compose-%D9%88%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85%D9%87%D8%A7-%D8%B6%D9%85%D9%86-%D9%86%D8%B8%D8%A7%D9%85-%D9%84%D9%8A%D9%86%D9%83%D8%B3-%D8%A3%D9%88%D8%A8%D9%88%D9%86%D8%AA%D9%88-r654/" rel="">تثبيت الأداة دوكر كومبوز Docker Compose واستخدامها ضمن نظام لينكس أوبونتو</a>.
		</p>
	</li>
	<li>
		<p>
			ثبّت عليه إنجن إكس Nginx بإتباع الإرشادات الواردة في مقال <a href="https://academy.hsoub.com/devops/servers/web/nginx/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-nginx-%D8%B9%D9%84%D9%89-%D8%A3%D9%88%D8%A8%D9%88%D9%86%D8%AA%D9%88-1804-r434/" rel="">كيفية تثبيت Nginx على أوبونتو 18.04</a>.
		</p>
	</li>
	<li>
		<p>
			أمّن حماية الخادم Nginx الموجود على المضيف بشهادات مصدقة من Let’s Encrypt مثلًا لحماية سجل Docker الخاص الذي تنشؤه، اطلّع على مقال <a href="https://academy.hsoub.com/devops/servers/web/nginx/%D9%83%D9%8A%D9%81-%D8%AA%D8%A4%D9%85%D9%91%D9%86-%D8%AE%D8%A7%D8%AF%D9%85-%D9%88%D9%8A%D8%A8-nginx-%D8%B9%D9%84%D9%89-%D8%A3%D9%88%D8%A8%D9%86%D8%AA%D9%88-1604-r365/" rel="">كيف تؤمّن خادم ويب NGINX على أوبنتو 16.04</a> على أكاديمية حسوب أو مقال <a href="https://www.digitalocean.com/community/tutorials/how-to-secure-nginx-with-let-s-encrypt-on-ubuntu-22-04" rel="external nofollow">تأمين خادم NGINX باستخدام Let’s Encrypt</a> على DigitalOcean لإنجاز هذه الخطوة. وتأكد من توجيه حركة مرور البيانات الواردة إلى تطبيقك من HTTP إلى HTTPS.
		</p>
	</li>
	<li>
		<p>
			احجز اسم نطاق لخادمك المضيف لسجل Docker، واسم النطاق المعتمد في المقال هو <code>your_domain</code>، وتوفير اسم النطاق ضروري قبل البدء بإعدادات <a href="https://academy.hsoub.com/devops/servers/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-%D8%AE%D8%AF%D9%85%D8%A9-let%E2%80%99s-encrypt-r352/" rel="">خدمة Let’s Encrypt</a>.
		</p>
	</li>
</ul>

<h2 id="1docker">
	الخطوة 1: تثبيت سجل Docker وإعداده
</h2>

<p>
	يفيدك تشغيل Docker من سطر الأوامر في بداية التشغيل وعند اختبار الحاويات، لكن استمرارك باستخدامه من سطر الأوامر لن يكون عمليًّا مع تقدم سير العمل، وبالأخص في عمليات النشر واسعة النطاق أو تلك التي تتطلب التحكم بعدة حاويات تعمل معًا على التوازي.
</p>

<p>
	لذا يستخدم المطورون أداة Docker Compose، حيث تكتب ملف <code>yml.</code> واحد لكل حاوية يتضمن إعداداتها والمعلومات التي تحتاجها للتواصل مع بقية الحاويات. ويمكنك استخدام تعليمة <code>docker compose</code> لتطبيق الأوامر دفعةً واحدة على جميع الأجزاء المكوّنة لتطبيقك والتحكم بها على أنها مجموعة.
</p>

<p>
	تُستخدم أداة Docker Compose أيضًا لإدارة سجل دوكر ومكوناته فهو تطبيق في نهاية الأمر ويتكون من عدة أجزاء.
</p>

<p>
	لتشغيل مثيل instance لسجل Docker على Docker Compose عليك إعداد الملف <code>docker-compose.yml</code> لتعريف السجل، وتوفير مساحة تخزينية له على القرص الصلب ليُخزّن البيانات.
</p>

<p>
	أنشئ في البداية مجلدًا اسمه <code>docker-registry</code> على الخادم المضيف وفق التالي، إذ سنُخزّن فيه إعدادات السجل:
</p>

<pre class="ipsCode">$ mkdir ~/docker-registry
</pre>

<p>
	انتقل إلى المجلد الجديد بكتابة التالي:
</p>

<pre class="ipsCode">$ cd ~/docker-registry
</pre>

<p>
	وأنشئ بداخله مجلدًا فرعيًّا باسم <code>data</code>، وفق الأمر التالي حيث سيُخزّن السجل صور الحاويات بداخله:
</p>

<pre class="ipsCode">$ mkdir data
</pre>

<p>
	أنشئ أيضًا ملفًا نصيًّا باسم <code>docker-compose.yml</code> باستعمال محرر نصوص مثل نانو كما يلي:
</p>

<pre class="ipsCode">$ nano docker-compose.yml
</pre>

<p>
	واكتب ضمنه المعلومات التالية، التي تُعرّف المثيل الأساسي لسجل Docker:
</p>

<pre class="ipsCode">version: '3'

services:
  registry:
    image: registry:latest
    ports:
    - "5000:5000"
    environment:
      REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /data
    volumes:
      - ./data:/data
</pre>

<p>
	لنشرح المعلومات السابقة، عرّفنا في البداية خدمةً جديدة باسم <code>registry</code>، وحددنا الصورة التي ستُبنى انطلاقًا منها وهي <code>registry</code>، أما الكلمة latest فتعني أنك تطلب استخدام أحدث إصدار متوفر من الصورة، وفي القسم التالي وجهّنا المنفذ port رقم <code>5000</code> على الخادم المضيف إلى المنفذ <code>5000</code> على الحاوية، وذلك يعني أن الطلبات الواردة إلى المنفذ <code>5000</code> على الخادم سترسل مباشرةً إلى السجل.
</p>

<p>
	أما في قسم بيئة العمل <code>environment</code> فقد أسندنا القيمة <code>data/</code> للمتغير <code>REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY</code> وهو المسؤول عن تحديد المجلد المخصص لتخزين بيانات السجل. وفي القسم <code>volumes</code> وصلنا المجلد <code>data/</code> على نظام ملفات الخادم المضيف بالمجلد <code>data/</code> داخل الحاوية، الذي يُعدّ بمثابة معبر فقط إذ ستُخزّن البيانات فعليًّا على الخادم المضيف.
</p>

<p>
	احفظ التغييرات على الملف، وأغلقه وشغّل الآن الإعدادات بتنفيذ الأمر التالي:
</p>

<pre class="ipsCode">$ docker compose up
</pre>

<p>
	سيبدأ تحميل حاوية السجل مع اعتمادياتها، وستصبح في وضع التشغيل، ثم ستحصل على خرج يشبه التالي:
</p>

<pre class="ipsCode">[+] Running 2/2
 ⠿ Network docker-registry_default       Created  0.1s
 ⠿ Container docker-registry-registry-1  Created  0.1s
Attaching to docker-registry-registry-1
docker-registry-registry-1  | time="2024-01-19T14:31:20.40444638Z" level=warning msg="No HTTP secret provided - generated random secret. This may cause problems with uploads if multiple registries are behind a load-balancer. To provide a shared secret, fill in http.secret in the configuration file or set the REGISTRY_HTTP_SECRET environment variable." go.version=go1.16.15 instance.id=4fb8d420-eaf8-4a69-b740-bdc94fa52d91 service=registry version="v2.8.1+unknown"
docker-registry-registry-1  | time="2024-01-19T14:31:20.404960549Z" level=info msg="redis not configured" go.version=go1.16.15 instance.id=4fb8d420-eaf8-4a69-b740-bdc94fa52d91 service=registry version="v2.8.1+unknown"
docker-registry-registry-1  | time="2024-01-19T14:31:20.412312462Z" level=info msg="using inmemory blob descriptor cache" go.version=go1.16.15 instance.id=4fb8d420-eaf8-4a69-b740-bdc94fa52d91 service=registry version="v2.8.1+unknown"
docker-registry-registry-1  | time="2024-01-19T14:31:20.412803878Z" level=info msg="Starting upload purge in 52m0s" go.version=go1.16.15 instance.id=4fb8d420-eaf8-4a69-b740-bdc94fa52d91 service=registry version="v2.8.1+unknown"
docker-registry-registry-1  | time="2024-01-19T14:31:20.41296431Z" level=info msg="listening on [::]:5000" go.version=go1.16.15 instance.id=4fb8d420-eaf8-4a69-b740-bdc94fa52d91 service=registry version="v2.8.1+unknown"
...
</pre>

<p>
	يتضمن الخرج السابق رسالة تحذير مفادها عدم توفر اتصال HTTP آمن <code>No HTTP secret provided</code>، لا تقلق سنعالجها في الفقرات القادمة.
</p>

<p>
	إذا دققت في السطر الأخير، ستجده يعلمك بإتمام عملية التشغيل بنجاح، وبأن السجل جاهز لاستقبال الطلبات على المنفذ <code>5000</code>.
</p>

<p>
	يمكنك الآن الضغط على <code>CTRL+C</code> لإيقاف التنفيذ.
</p>

<p>
	إذًا فقد أنشأنا في هذه الخطوة إعدادات Docker Compose التي شغّلت سجل Docker على المنفذ <code>5000</code>، وسنعمل في الخطوات التالية على استعراضه باسم النطاق المخصص له، وعلى ضبط إعدادات المصادقة Authentication للتحكم بصلاحية الوصول إليه.
</p>

<h2 id="2nginx">
	الخطوة 2: ضبط إعدادات التوجيه لمنفذ Nginx
</h2>

<p>
	ذكرنا في بداية المقال أن تفعيل بروتوكول HTTPS على اسم نطاقك هو أحد متطلبات العمل الأولية، وسنعمل الآن على توجيه حركة مرور البيانات من اسم النطاق إلى حاوية السجل لنضمن أن الوصول لسجل Docker سيجري عبر اتصالٍ آمن.
</p>

<p>
	لابد أنك جهزت الملف <code>etc/nginx/sites-available/your_domain/</code> أثناء إعدادك لخادم Nginx، إذ يحتوى هذا الملف على قسمٍ خاص بإعدادات الخادم، افتحه بواسطة أي محرر نصوص وفق التالي، لنجري عليه بعض التعديلات:
</p>

<pre class="ipsCode">$ sudo nano /etc/nginx/sites-available/your_domain
</pre>

<p>
	ابحث ضمنه عن القسم المسمى <code>location</code>:
</p>

<pre class="ipsCode">...
        location / {
  ...
        }
...
</pre>

<p>
	المطلوب في حالتنا أمران: توجيه حركة مرور البيانات إلى المنفذ <code>5000</code> الذي يتلقى السجل عبره الطلبات، وإضافة ترويسات headers للطلبات الموجهة إلى السجل تحتوي معلوماتٍ إضافية عنها يضيفها الخادم. استبدل محتوى القسم <code>location</code> بالتالي لتنفيذهما:
</p>

<pre class="ipsCode">...
location / {
    # Do not allow connections from docker 1.5 and earlier
    # docker pre-1.6.0 did not properly set the user agent on ping, catch "Go *" user agents
    if ($http_user_agent ~ "^(docker\/1\.(3|4|5(?!\.[0-9]-dev))|Go ).*$" ) {
      return 404;
    }

    proxy_pass                          http://localhost:5000;
    proxy_set_header  Host              $http_host;   # required for docker client's sake
    proxy_set_header  X-Real-IP         $remote_addr; # pass on real client's IP
    proxy_set_header  X-Forwarded-For   $proxy_add_x_forwarded_for;
    proxy_set_header  X-Forwarded-Proto $scheme;
    proxy_read_timeout                  900;
}
...
</pre>

<p>
	تتأكد الجملة <code>if</code> من توفر عدة شروط قبل أن تسمح للطلب بالمرور إلى السجل، فتتحقق من وكيل المستخدم صاحب الطلب User Agent، ومن كون إصدار Docker الذي يستعمله أعلى من 1.5، ومن أنه ليس تطبيق مبرمج بلغة <code>Go</code> ويسعى للوصول إلى السجل. يمكنك معرفة المزيد عن ترويسة <code>nginx</code> بمراجعة <a href="https://docs.docker.com/registry/recipes/nginx/#setting-things-up" rel="external nofollow">دليل إعداد Ngin لسجل Docker</a> من توثيقات Docker الرسمية.
</p>

<p>
	احفظ التغييرات على الملف، وأعِد تشغيل Nginx بكتابة التعليمة التالية، حتى تأخذ التغييرات مفعولها:
</p>

<pre class="ipsCode">$ sudo systemctl restart nginx
</pre>

<p>
	إذا حصلت على أي رسالة خطأ تفيد بعدم نجاح عملية إعادة التشغيل، فتحقق مجددًا من صحة التعديلات التي أجريتها على الملف.
</p>

<p>
	سنشغل السجل الآن لنتأكد من توجيه Nginx الطلبات الواردة إلى حاوية السجل، اكتب الأمر التالي:
</p>

<pre class="ipsCode">$ docker compose up
</pre>

<p>
	استعرض العنوان التالي في متصفحك، والذي يتضمن اسم النطاق يليه <code>v2</code> نقطة الوصول endpoint:
</p>

<pre class="ipsCode">https://your_domain/v2
</pre>

<p>
	سيعرض لك المتصفح كائن <a href="https://academy.hsoub.com/programming/javascript/%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D8%A8%D8%B5%D9%8A%D8%BA%D8%A9-json-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-%D8%B3%D9%83%D8%B1%D9%8A%D8%A8%D8%AA-r2318/" rel="">JSON</a> فارغ على الشكل:
</p>

<p style="text-align: center;">
	<img class="ipsImage ipsImage_thumbnailed" data-fileid="153771" data-ratio="16.80" data-unique="wwlbg981b" style="width: 500px; height: auto;" width="894" alt="IMG-01-private-docker-registry.png" src="https://academy.hsoub.com/uploads/monthly_2024_07/IMG-01-private-docker-registry.png.9d78cc65aaf7345ec03c66265fe8e790.png">
</p>

<p>
	وستحصل في الطرفية على الخرج التالي:
</p>

<pre class="ipsCode">docker-registry-registry-1  | time="2024-01-19T14:32:50.082396361Z" level=info msg="response completed" go.version=go1.16.15 http.request.host=your_domain http.request.id=779fe265-1a7c-4a15-8ae4-eeb5fc35de98 http.request.method=GET http.request.remoteaddr=87.116.166.89 http.request.uri="/v2" http.request.useragent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36" http.response.contenttype="text/html; charset=utf-8" http.response.duration="162.546µs" http.response.status=301 http.response.written=39
docker-registry-registry-1  | 172.19.0.1 - - [19/Nov/2022:14:32:50 +0000] "GET /v2 HTTP/1.0" 301 39 "" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36"
docker-registry-registry-1  | 172.19.0.1 - - [19/Nov/2022:14:32:50 +0000] "GET /v2/ HTTP/1.0" 200 2 "" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36"
docker-registry-registry-1  | time="2024-01-19T14:32:50.132472674Z" level=info msg="response completed" go.version=go1.16.15 http.request.host=your_domain http.request.id=0ffb17f0-c2a0-49d6-94f3-af046cfb96e5 http.request.method=GET http.request.remoteaddr=87.116.166.89 http.request.uri="/v2/" http.request.useragent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36" http.response.contenttype="application/json; charset=utf-8" http.response.duration=2.429608ms http.response.status=200 http.response.written=2
</pre>

<p>
	يخبرك السطر الأخير أن الطلب <code>GET</code> قد وصل إلى نقطة الوصول <code>/v2/</code> المذكورة في العنوان الذي طلبته من المتصفح، واستقبلته حاوية السجل (بفضل إعدادات التوجيه التي عملنا عليها) وأرسلت استجابة بكائن JSON <code>{}</code> مع رمز الاستجابة <code>200</code> الذي يشير لنجاح العملية.
</p>

<p>
	اضغط الآن <code>CTRL+C</code> لإيقاف التنفيذ.
</p>

<p>
	أنهينا بذلك إعدادات التوجيه وننتقل لإعدادات تأمين السجل.
</p>

<h2 id="3authentication">
	الخطوة 3: ضبط إعدادات المصادقة Authentication
</h2>

<p>
	يتيح Nginx لمستخدمه إعداد آلية مصادقة Authentication باسم مستخدم وكلمة مرور لتقييد الوصول إلى مواقعهم المستضافة عليه وذلك بإنشاء ملف مصادقة <code>htpasswd</code> وكتابة أسماء المستخدمين المسموح له بالوصول إليه مع كلمات مرورهم. سنستخدم هذه الآلية هنا لحماية سجل Docker.
</p>

<p>
	يمكنك الحصول على الأداة <code>htpasswd</code> بتثبيت الحزمة <code>apache2-utils</code> وفق الأمر التالي:
</p>

<pre class="ipsCode">$ sudo apt install apache2-utils -y
</pre>

<p>
	سننشئ الآن المجلد <code>docker-registry/auth/~</code> لتخزين ملف المصادقة الذي يتضمن بيانات الاعتماد، وفق ما يلي:
</p>

<pre class="ipsCode">$ mkdir ~/docker-registry/auth
</pre>

<p>
	انتقل للمجلد الجديد بكتابة التالي:
</p>

<pre class="ipsCode">$ cd ~/docker-registry/auth
</pre>

<p>
	نفذّ الأمر المبين أدناه لإنشاء المستخدم الأول، واستبدل العبارة <code>username</code> باسم المستخدم الفعلي، واحرص على كتابة الراية <code>B-</code> فهي مسؤولة عن تفعيل خاصية التشفير <code>bcrypt</code> التي تشترطها Docker:
</p>

<pre class="ipsCode">$ htpasswd -Bc registry.password username
</pre>

<p>
	سيُطلب منك إدخال كلمة المرور الخاصة بهذا المستخدم، أدخلها بدقة. وستخزن بيانات الاعتماد هذه التي أدخلتها في <code>registry.password</code>.
</p>

<p>
	<strong>ملاحظة:</strong> أعِدّ تنفيذ الأمر السابق بدون الراية <code>c-</code> لإضافة مستخدمين آخرين إلى الملف وفق التالي:
</p>

<pre class="ipsCode">$ htpasswd -B registry.password username
</pre>

<p>
	إذ تشير الراية <code>c-</code> إلى إنشاء ملف جديد، وتعني إزالتها التعديل على الملف الحالي (أي إضافة مستخدمين جدد).
</p>

<p>
	عدّل الآن الملف <code>docker-compose.yml</code> ليستخدم Docker ملف بيانات الاعتماد -الذي أنشأناه- لإجراء المصادقة مع المستخدمين للتحقق من هوياتهم. افتح أولًا الملف بكتابة التالي:
</p>

<pre class="ipsCode">$ nano ~/docker-registry/docker-compose.yml
</pre>

<p>
	أضف الأجزاء المتعلقة بالمصادقة إلى محتواه، ليصبح كما يلي:
</p>

<pre class="ipsCode">version: '3'

services:
  registry:
    image: registry:latest
    ports:
    - "5000:5000"
    environment:
      REGISTRY_AUTH: htpasswd
      REGISTRY_AUTH_HTPASSWD_REALM: Registry
      REGISTRY_AUTH_HTPASSWD_PATH: /auth/registry.password
      REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /data
    volumes:
      - ./auth:/auth
      - ./data:/data
</pre>

<p>
	أضفت بهذه التعديلات بعض المتغيرات إلى متغيرات البيئة لفرض استخدام المصادقة مع بروتوكول HTTP، ولتوفير مسار الملف <code>htpasswd</code>. فقد حدد المتغير <code>REGISTRY_AUTH</code> الذي يحمل القيمة <code>htpasswd</code> مخطط المصادقة المستخدم أو authentication scheme، وأُسندت القيمة التي تدل على مسار ملف المصادقة إلى المتغير <code>REGISTRY_AUTH_HTPASSWD_PATH</code>، أما المتغير <code>REGISTRY_AUTH_HTPASSWD_REALM</code> فيوضح نطاق تنفيذ المصادقة <code>htpasswd</code>.
</p>

<p>
	وفي السطر ما قبل الأخير وصلت المجلد <code>auth/.</code> إلى داخل حاوية السجل ليكون متاحًا ضمنها. احفظ التغييرات على الملف وأغلقه.
</p>

<p>
	ودعنا نتأكد من استخدام السجل لإجراء المصادقة. توجه في البداية إلى مجلد السجل الأساسي بكتابة الأمر:
</p>

<pre class="ipsCode">$ cd ~/docker-registry
</pre>

<p>
	شغّل السجل بتنفيذ ما يلي:
</p>

<pre class="ipsCode">$ docker compose up
</pre>

<p>
	حدّث الصفحة في متصفح الويب إذا كان ما يزال مفتوحًا لديك، أو اطلب مجددًا اسم النطاق الذي حددته للسجل، ولاحظ الفرق. سيطلب منك في هذه المرة اسم مستخدم وكلمة مرور.
</p>

<p>
	أدخل البيانات الصحيحة وستحصل على الخرج السابق نفسه، كائن JSON فارغ كما يلي، وهو ما يشير لصحة التنفيذ:
</p>

<p style="text-align: center;">
	<img class="ipsImage ipsImage_thumbnailed" data-fileid="153771" data-ratio="16.80" data-unique="twhvygklm" style="width: 500px; height: auto;" width="894" alt="IMG-01-private-docker-registry.png" src="https://academy.hsoub.com/uploads/monthly_2024_07/IMG-01-private-docker-registry.png.9d78cc65aaf7345ec03c66265fe8e790.png">
</p>

<p>
	نجحت إذا عملية المصادقة وسُمح لك بالوصول للسجل بعد إدخال بيانات الاعتماد الصحيحة. يمكنك الخروج بالضغط على <code>CTRL+C</code> في نافذة الطرفية.
</p>

<p>
	خطوتنا التالية هي تحويل السجل إلى خدمة تعمل في الخلفية، وتقلع تلقائيًا، مع الإبقاء على المرونة التي تسمح لنا بإعادة تشغيلها.
</p>

<h2 id="4docker">
	الخطوة 4: بدء تشغيل سجل Docker بصفته خدمة
</h2>

<p>
	يعني تحويل سجل Docker إلى خدمة ضبط بعض الإعدادات في Docker Compose لإبقاء حاوية السجل في وضع التشغيل دائمًا، فتُقلع تلقائيًا مع إقلاع نظام التشغيل، ويُعاد تشغيلها بعد أي عطل.
</p>

<p>
	اكتب الأمر التالي، وافتح الملف <code>docker-compose.yml</code> لنجري عليه بعض التعديلات:
</p>

<pre class="ipsCode">$ nano docker-compose.yml
</pre>

<p>
	ابحث ضمن الملف عن قسم السجل المسمى <code>registry</code>، واكتب تحته السطر التالي:
</p>

<pre class="ipsCode">...
  registry:
    restart: always
...
</pre>

<p>
	يعني ضبط المحدد <code>restart</code> على القيمة <code>always</code> أن حاوية السجل سيُعاد تشغيلها دائمًا بعد أي طارئ يسبب إيقافها. احفظ الآن التغييرات على الملف، وأغلقه لننتقل إلى الإجراء التالي.
</p>

<p>
	اكتب الأمر المبين أدناه لبدء تشغيل حاوية السجل بصفتها عملية تعمل في الخلفية background process، وذلك بتمرير الراية <code>d-</code>:
</p>

<pre class="ipsCode">$ docker compose up -d
</pre>

<p>
	يمكنك إغلاق جلسة <abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة"><abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة">SSH</abbr></abbr> والاطمئنان بأن حاوية السجل لن تتوقف فهي الآن تعمل في الخلفية.
</p>

<p>
	ستتناول الخطوة التالية زيادة حجم الملفات التي يُسمح برفعها على خادم Nginx ليناسب حجوم <a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D8%A8%D9%86%D8%A7%D8%A1-%D8%A7%D9%84%D8%B5%D9%88%D8%B1-%D9%88%D8%AA%D9%87%D9%8A%D8%A6%D8%A9-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%84%D9%84%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D8%A8%D9%86%D9%8A%D8%A9-%D8%B6%D9%85%D9%86-%D8%A7%D9%84%D8%AD%D8%A7%D9%88%D9%8A%D8%A7%D8%AA-r642/" rel="">صور الحاويات</a> التي ستحفظ على السجل.
</p>

<h2 id="5nginx">
	الخطوة 5: زيادة حجم الملفات المسموح رفعها على Nginx
</h2>

<p>
	الحجم الأعظمي المسموح رفعه على خادم Nginx افتراضيًا هو <code>1m</code> أي 1 ميجا بايت للملف الواحد، ويُعدّ صغيرًا نسبيًا موازنةً بحجوم صور الحاويات، لذا يتحتم علينا تغيره قبل البدء برفع الصور إلى السجل. يمكنك تغييره بتعديل قيمته في <a href="https://academy.hsoub.com/devops/servers/web/nginx/%D9%81%D9%87%D9%85-%D8%A8%D9%86%D9%8A%D8%A9-%D9%85%D9%84%D9%81-%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF%D8%A7%D8%AA-nginx-%D9%88%D8%B3%D9%8A%D8%A7%D9%82%D8%A7%D8%AA-%D8%A7%D9%84%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF%D8%A7%D8%AA-r112/" rel="">ملف إعدادات Nginx</a> الموجود في المسار <code>etc/nginx/nginx.conf/</code>.
</p>

<p>
	افتح ملف إعدادات Nginx بكتابة الأمر التالي:
</p>

<pre class="ipsCode">$ sudo nano /etc/nginx/nginx.conf
</pre>

<p>
	أضف السطر التالي إلى القسم <code>http</code> ضمنه:
</p>

<pre class="ipsCode">...
http {
        client_max_body_size 16384m;
        ...
}
...
</pre>

<p>
	زدنا بهذا التعديل الحجم الأعظمي للملف المسموح برفعه إلى 16 جيجا بايت، وذلك بضبط قيمة المحدد <code>client_max_body_size</code> على <code>16384m</code>.
</p>

<p>
	احفظ التغييرات على الملف وأغلقه.
</p>

<p>
	أعِدّ تشغيل الخادم Nginx لتأخذ التغييرات مفعولها، وفق التالي:
</p>

<pre class="ipsCode">$ sudo systemctl restart nginx
</pre>

<p>
	يمكنك الآن رفع الصور إلى السجلات بدون أي أخطاء تتعلق بالحجم من Nginx.
</p>

<h2 id="6docker">
	الخطوة 6: نشر صور الحاويات على سجل Docker الخاص
</h2>

<p>
	أصبح خادم السجل قادرًا على استيعاب الملفات كبيرة الحجم، لذا سنجرب نشر صورة تجريبية عليه، فإذا لم يتوفر لديك أي صورة لرفعها، يمكنك تحميل صورة أوبنتو من Docker Hub (سجل Docker العام) لتجرب نشرها على هذا السجل.
</p>

<p>
	افتح جلسة طرفية جديدة على الخادم العميل، ونفذّ الأمر المبين أدناه لتحميل صورة الحاوية <code>ubuntu</code> وتشغيلها (تذكر أننا طلبنا وجود خادمين ضمن المتطلبات الأولية خادم مضيف وخادم عميل):
</p>

<pre class="ipsCode">$ docker run -t -i ubuntu /bin/bash
</pre>

<p>
	تمنحك الرايتان <code>t-</code> و <code>i-</code> واجهة صدفة shell تفاعلية لتُنفذ بواسطتها الأوامر داخل حاوية أوبنتو.
</p>

<p>
	أنشئ الآن ملفًا يدعى <code>SUCCESS</code> داخل حاوية أوبنتو وفق التالي:
</p>

<pre class="ipsCode">root@f7e13d5464d1:/# touch /SUCCESS
</pre>

<p>
	أنشأنا هذا الملف داخل الحاوية كنوع من التخصيص لتمييزها عن غيرها، فيمكننا لاحقًا استعراضه للتأكد من استخدامنا الحاوية الصحيحة.
</p>

<p>
	اخرج من صدفة الحاوية بكتابة التالي:
</p>

<pre class="ipsCode">root@f7e13d5464d1:/# exit
</pre>

<p>
	أنشئ الآن صورة عن هذه الحاوية بعد تخصيصها، بكتابة الأمر التالي:
</p>

<pre class="ipsCode">$ docker commit $(docker ps -lq) test-image
</pre>

<p>
	أصبح لديك صورة عن حاوية أوبنتو المخصصة محفوظة محليًا على خادمك، سجِّل دخول إلى سجل Docker وفق التالي لنحاول نشرها عليه:
</p>

<pre class="ipsCode">$ docker login https://your_domain
</pre>

<p>
	سيُطلب منك إدخال اسم مستخدم وكلمة مرور، أدخل بيانات أحد المستخدمين الذين أنشأتهم في الخطوة 3 قبل قليل، وستحصل على الخرج التالي:
</p>

<pre class="ipsCode">Login Succeeded
</pre>

<p>
	بعد تسجيل الدخول بنجاح عدّل تسمية صورة الحاوية كما يلي:
</p>

<pre class="ipsCode">$ docker tag test-image your_domain/test-image
</pre>

<p>
	انشرها الآن على سجلك بكتابة الأمر التالي:
</p>

<pre class="ipsCode">$ docker push your_domain/test-image
</pre>

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

<pre class="ipsCode">Using default tag: latest
The push refers to a repository [your_domain/test-image]
1cf9c9034825: Pushed
f4a670ac65b6: Pushed
latest: digest: sha256:95112d0af51e5470d74ead77932954baca3053e04d201ac4639bdf46d5cd515b size: 736
</pre>

<p>
	إذًا فقد نشرنا صورة حاوية Docker على السجل الخاص، وتأكدنا من فعالية عملية المصادقة، إذ لم نتمكن من الوصول للسجل والنشر عليه بدون إدخال اسم مستخدم وكلمة مرور صحيحين، لنختبر الآن سحب الصور من السجل.
</p>

<h2 id="docker">
	سحب الصور من سجل Docker الخاص
</h2>

<p>
	سنحاول سحب الصورة نفسها التي نشرتها على السجل في الخطوة السابقة.
</p>

<p>
	اكتب الأمر التالي، وسجل دخول إلى سجل Docker من الخادم الرئيسي باستخدام بيانات المستخدمين الذين أنشأتهم سابقًا:
</p>

<pre class="ipsCode">$ docker login https://your_domain
</pre>

<p>
	جرّب سحب الصورة <code>test-image</code> من السجل كما يلي:
</p>

<pre class="ipsCode">$ docker pull your_domain/test-image
</pre>

<p>
	حمَّل Docker الآن هذه الصورة إلى خادمك المحلي، شغّل حاوية جديدة باستخدامها عبر كتابة ما يلي:
</p>

<pre class="ipsCode">$ docker run -it your_domain/test-image /bin/bash
</pre>

<p>
	يوفر لنا هذا الأمر صدفة shell تفاعلية مع الحاوية المُشغّلة.
</p>

<p>
	اكتب ضمنها الأمر التالي لنستعرض نظام ملفاتها:
</p>

<pre class="ipsCode">root@f7e13d5464d1:/# ls
</pre>

<p>
	لاحظ الخرج التالي، إنه يتضمن الملف <code>SUCCESS</code> الذي أنشأناه قبلًا لتمييز صورة الحاوية قبل نشرها على السجل، وهذا يؤكد أن الحاوية المُشغّلة حاليًّا مبنية على الصورة نفسها المسحوبة من السجل:
</p>

<pre class="ipsCode">root@f7e13d5464d1:/# SUCCESS  bin  boot  dev  etc  home  lib  lib64  media   mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
</pre>

<p>
	اخرج الآن من صدفة الحاوية بكتابة الأمر التالي:
</p>

<pre class="ipsCode">$ exit
</pre>

<p>
	أنهينا بذلك إنشاء سجل Docker خاص وآمن لتخزين صور الحاويات التي تخصصها حسب احتياجاتك، وجربنا معًا نشر صورة تجريبية عليه وسحبها منه إلى الخادم المحلي.
</p>

<h2 id="-1">
	الخلاصة
</h2>

<p>
	يساعدك هذا المقال التعليمي على إنشاء سجلك الخاص لحفظ صور حاويات Docker ونشر الصور عليه بمرونة وأمان، ويمكنك أيضًا الاستفادة من بعض الأدوات الخاصة <a href="https://academy.hsoub.com/devops/deployment/%D8%A3%D9%81%D8%B6%D9%84-%D9%85%D9%85%D8%A7%D8%B1%D8%B3%D8%A7%D8%AA-%D9%85%D9%86%D9%87%D8%AC-%D8%A7%D9%84%D8%AA%D9%83%D8%A7%D9%85%D9%84-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D9%85%D8%B1-%D9%88%D8%A7%D9%84%D8%AA%D8%B3%D9%84%D9%8A%D9%85-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D9%85%D8%B1-cicd-r687/" rel="">بالتكامل المستمر</a> لأتمتة عمليات النشر عليه.
</p>

<p>
	وتذكر دائمًا أن اعتمادك على حاويات Docker في سير عملك يعني أن الصور التي تتضمن الشيفرات البرمجية لتطبيقاتك ستعمل دائمًا بالصورة المطلوبة نفسها على أي جهاز وفي أي بيئة العمل سواءً في مرحلة التطوير أو الإنتاج. لمزيد من المعلومات عن حاويات Docker وتفاصيل التعامل معها، وطرق كتابة ملفات Docker، ننصحك بالاطلاع على مقالات <a href="https://academy.hsoub.com/devops/cloud-computing/docker/" rel="">قسم Docker</a> باللغة العربية على أكاديمية حسوب أو على <a href="https://docs.docker.com/develop/develop-images/dockerfile_best-practices/" rel="external nofollow">توثيقات Docker الرسمية</a>.
</p>

<p>
	ترجمة -وبتصرف- للمقال <a href="https://www.digitalocean.com/community/tutorials/how-to-set-up-a-private-docker-registry-on-ubuntu-22-04" rel="external nofollow">How To Set Up a Private Docker Registry on Ubuntu 22.04</a> لصاحبيه Young Kim و Savic.
</p>

<p>
	اقرأ أيضًا
</p>

<ul>
	<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D9%85%D8%A7-%D9%87%D9%8A-%D8%AA%D9%82%D9%86%D9%8A%D8%A9-docker%D8%9F-r639/" rel="">ما هي تقنية Docker؟</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D8%A3%D9%85%D8%B1-docker-exec-%D9%81%D9%8A-%D8%AD%D8%A7%D9%88%D9%8A%D8%A7%D8%AA-docker-r813/" rel="">استخدام الأمر docker exec في حاويات Docker</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%AF%D9%88%D9%83%D8%B1-docker-r607/" rel="">مدخل إلى دوكر Docker</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D8%A7%D9%84%D8%A3%D8%AF%D8%A7%D8%A9-%D8%AF%D9%88%D9%83%D8%B1-%D9%83%D9%88%D9%85%D8%A8%D9%88%D8%B2-docker-compose-%D9%88%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85%D9%87%D8%A7-%D8%B6%D9%85%D9%86-%D9%86%D8%B8%D8%A7%D9%85-%D9%84%D9%8A%D9%86%D9%83%D8%B3-%D8%A3%D9%88%D8%A8%D9%88%D9%86%D8%AA%D9%88-r654/" rel="">تثبيت الأداة دوكر كومبوز Docker Compose واستخدامها ضمن نظام لينكس أوبونتو</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">814</guid><pubDate>Mon, 22 Jul 2024 15:09:03 +0000</pubDate></item><item><title>&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x627;&#x644;&#x623;&#x645;&#x631; docker exec &#x641;&#x64A; &#x62D;&#x627;&#x648;&#x64A;&#x627;&#x62A; Docker</title><link>https://academy.hsoub.com/devops/cloud-computing/docker/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D8%A3%D9%85%D8%B1-docker-exec-%D9%81%D9%8A-%D8%AD%D8%A7%D9%88%D9%8A%D8%A7%D8%AA-docker-r813/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2024_07/dockerexecDocker.png.8c258051ac3db4b6c824474d706fc76b.png" /></p>
<p>
	تعد دوكر Docker واحدة من الأدوات المشهورة والمستخدمة على نطاق واسع لتغليف التطبيقات ضمن حاويات containerization، وهي تساعد المطورين على إنشاء وإدارة حاويات لينكس المحمولة والمتسقة فيما بينها إذ توفر دوكر بيئة معزولة لتشغيل التطبيقات مما يسهل نقلها وتشغيلها عبر مختلف البيئات دون مشاكل التوافق.
</p>

<p>
	ويحتاج المطورون عند تطوير الحاويات ونشرها إلى أوامر برمجية تعينهم على مراقبة حالتها في أثناء التشغيل أو حل مشكلاتها، وهذا ما يوفره الأمر <code>docker exec</code> الذي يسمح لك بتشغيل البرامج في حاويات Docker مشغلة مسبقًا.
</p>

<p>
	سنوضح في مقال اليوم ما هو الأمر <code>docker exec</code>؟ وما هي خياراته؟ وكيف تستخدمه لتنفيذ الأوامر في الحاويات قيد التشغيل وطريقة الحصول على واجهة صدفة shell تفاعلية في داخل الحاوية، كما سنعرفك على أبرز الأخطاء التي قد تظهر لك عند تنفيذ هذا الأمر وكيفية التعامل معها وحلها.
</p>

<h2 id="">
	متطلبات العمل
</h2>

<p>
	يتطلب تطبيق خطوات المقال وجود Docker مُثبتًا على حاسوبك أو خادمك الذي ستعمل منه، ومستخدمًا يمتلك صلاحيات تشغيل الأمر <code>docker</code>، وإذا احتجت لتشغيل <code>docker</code> بصلاحية الجذر <strong>root</strong> احرص على إضافة <code>sudo</code> قبل كتابتك الأوامر الواردة هنا.
</p>

<p>
	يمكنك الاطلاع على مقال <a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D8%AF%D9%88%D9%83%D8%B1-%D9%88%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85%D9%87-%D8%B9%D9%84%D9%89-%D8%AF%D8%A8%D9%8A%D8%A7%D9%86-r465/" rel="">كيفية تثبيت دوكر واستخدامه على دبيان</a> لمعرفة خطوات تثبيت Docker، وستجد فيه فقرةً خاصة عن كيفية استخدام Docker دون كتابة <code>sudo</code> في بداية كل أمر في حال فضلت عدم كتابتها.
</p>

<h2 id="-1">
	تشغيل حاوية تجريبية
</h2>

<p>
	يُستخدم الأمر <code>docker exec</code> كما ذكرنا سابقًا مع الحاويات قيد التشغيل، لذا إن لم يكن لديك حاوية تعمل أنشئ واحدة تجريبية وشغّلها باستخدام الأمر <code>docker run</code> وفق التالي:
</p>

<pre class="ipsCode">$ docker run -d --name container-name alpine watch "date &gt;&gt; /var/log/date.log"
</pre>

<p>
	يُنشئ هذا الأمر حاويةً جديدة بالاعتماد صورة image لتوزيعة Alpine الرسمية الموجود في <a href="https://hub.docker.com/_/alpine" rel="external nofollow">مستودعات دوكر</a>. إذ إن <a href="https://alpinelinux.org/" rel="external nofollow">Alpine Linux</a> من أشهر توزيعات لينكس المستخدمة مع الحاويات بسبب خفتها في استخدام الموارد وصغر حجمها.
</p>

<p>
	تشير الراية <code>d-</code> إلى فصل الحاوية عن نافذة الطرفية، فتعمل الحاوية في الخلفية، أما <code>name container-name--</code> فيُحدد اسم الحاوية، سُميّت الحاوية هنا <code>container-name</code>، اكتب الاسم الذي تريده أو يمكنك عدم كتابة أي اسم وترك دوكر Docker يعطي الحاوية اسمًا عشوائيًا فريدًا.
</p>

<p>
	ويأتي بعد اسم الحاوية اسم الصورة التي ستُنشئ الحاوية انطلاقًا منها، واسمها في حالتنا <code>alpine</code>.
</p>

<p>
	وبعدها اكتب الأمر الذي تريد تشغيله داخل الحاوية، وهو في مثالنا <code>"watch "date &gt;&gt; /var/log/date.log</code>. افتراضيًا يكرر الأمر <code>watch</code> تشغيل الأمر المكتوب بعده أي <code>"date &gt;&gt; /var/log/date.log"</code> كل ثانيتين، ويعرض الأمر <code>date</code> التاريخ والوقت الحاليين على الخادم، كما يلي:
</p>

<pre class="ipsCode">Fri Jan 26 14:57:05 UTC 2024
</pre>

<p>
	أما الجزء <code>/var/log/date.log/ &lt;&lt;</code> فيعني أن نتيجة تنفيذ الأمر <code>date</code> ستُخزن في ملف نصي يدعى <code>/var/log/date.log/</code>، وبالتالي سيُكتب سطرٌ جديد كل ثانيتين في هذا الملف، وبعد عدة ثواني سيبدو محتواه على الشكل التالي:
</p>

<pre class="ipsCode">Fri Jan 26 15:00:26 UTC 2024
Fri Jan 26 15:00:28 UTC 2024
Fri Jan 26 15:00:30 UTC 2024
Fri Jan 26 15:00:32 UTC 2024
Fri Jan 26 15:00:34 UTC 2024
</pre>

<p>
	سنعرض في الفقرة التالية طريقة اكتشاف أسماء الحاويات المشغلة على خادم معين، لتستفيد منها في الحالات التي تستهدف فيها العمل على حاوية لا تعرف اسمها بدقة.
</p>

<h2 id="docker">
	اكتشاف اسم حاوية Docker
</h2>

<p>
	بما أن الأمر <code>docker exec</code> يعمل مع الحاويات قيد التشغيل، لذا من المهم معرفة اسم الحاوية أو المعرّف الخاص بها Container ID لتمريره له، ويمكنك معرفة أسماء الحاويات ومعلوماتٍ أخرى عنها بواسطة الأمر <code>docker ps</code> كما يلي:
</p>

<pre class="ipsCode">$ docker ps
</pre>

<p>
	يعرض لك هذا الأمر أسماء الحاويات التي تعمل على الخادم، مع معلوماتٍ عامة عنها، مثل: صورة الحاوية، والأمر الذي تنفذه الآن، وحالة تشغيلها وغيرها، ألقِ نظرة على خرج الأمر <code>docker ps</code>:
</p>

<pre class="ipsCode">CONTAINER ID   IMAGE     COMMAND                  CREATED          STATUS          PORTS     NAMES
76aded7112d4   alpine    "watch 'date &gt;&gt; /var…"   11 seconds ago   Up 10 seconds             container-name
</pre>

<p>
	ستلاحظ في الخرج السابق وجود اسم الحاوية والمُعرّف الخاص بها، وهما ينوبان عن بعضهما، فيمكنك تمرير أي واحد منهما للأمر <code>docker exec</code> للتعامل مع الحاوية.
</p>

<p>
	وإذا رغبت بتغيير اسم الحاوية مثلًا، فاستخدم الأمر <code>docker rename</code>، وفق التالي:
</p>

<pre class="ipsCode">$ docker rename container-name new-name
</pre>

<p>
	الآن بعد أن تعلمنا كيف نحصل على اسم الحاوية، سنعرض بعضًا من الأمثلة العملية على طريقة استخدام <code>docker exec</code> لتنفيذ الأوامر في حاوية قيد التشغيل.
</p>

<h2 id="selldocker">
	تشغيل صدفة Sell تفاعلية في حاوية Docker
</h2>

<p>
	تحتاج في كثير من الحالات لتشغيل <a href="https://academy.hsoub.com/devops/linux/redhat/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D8%B5%D8%AF%D9%81%D8%A9-shell-%D9%81%D9%8A-red-hat-enterprise-linux-r736/" rel="">صدفة Shell</a> تفاعلية داخل حاوية Docker لاستعراض نظام ملفاتها مثلًا أو لتقصي أخطاء بعض العمليات ضمنها وتصحيحها في أثناء التنفيذ أو لغير ذلك من الأسباب، يوفر لك <code>docker exec</code> هذه الإمكانية عبر استخدامه مع الرايتين <code>i-</code> و <code>t-</code>.
</p>

<p>
	تُبقي الراية <code>i-</code> إمكانية إدخال البيانات إلى الحاوية متاحة، بينما تُنشئ الراية <code>t-</code> طرفيةً وهمية pseudo-terminal ترتبط بالصدفة shell، ويمكنك استخدام الرايتين معًا في أمرٍ واحد وفق التالي:
</p>

<pre class="ipsCode">$ docker exec -it container-name sh
</pre>

<p>
	ينتج عن الأمر السابق تشغيل الصدفة Shell أو <code>sh</code> في الحاوية المحددة، وفتح مِحث أوامر Shell prompt اعتيادي لاستخدامه. وتستطيع الخروج من الحاوية بكتابة <code>exit</code> ثم الضغط على <code>ENTER</code>.
</p>

<pre class="ipsCode">/ # exit
</pre>

<p>
	إذا احتوت صورة الحاوية التي تستخدمها صدفةً Shell متقدمة مثل <a href="https://academy.hsoub.com/devops/linux/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%B5%D8%AF%D9%81%D8%A9-%D8%A8%D8%A7%D8%B4-bash-r606/" rel="">باش Bash</a>، فيمكنك العمل معها باستبدال الرمز <code>sh</code> في الأمر السابق بالرمز <code>bash</code>.
</p>

<h2 id="docker-1">
	تشغيل أوامر غير تفاعلية في حاوية Docker
</h2>

<p>
	يمكنك استخدام <code>docker exec</code> بلا رايات Flags، لتنفيذ الأوامر التي لا تتطلب تفاعلًا مع حاويات Docker قيد التشغيل، ألقِ نظرة على المثال التالي:
</p>

<pre class="ipsCode">$ docker exec container-name tail /var/log/date.log
</pre>

<p>
	يُشغّل السطر السابق الأمر <code>tail /var/log/date.log</code> في الحاوية المسماة <code>container-name</code> ثم يعرض لك النتائج، إذ يُظهِر الأمر <code>tail</code> آخر عشر أسطر من ملف الخرج على الشاشة، والذي يبدو مثل التالي:
</p>

<pre class="ipsCode">Mon Jan 29 14:39:33 UTC 2024
Mon Jan 29 14:39:35 UTC 2024
Mon Jan 29 14:39:37 UTC 2024
Mon Jan 29 14:39:39 UTC 2024
Mon Jan 29 14:39:41 UTC 2024
Mon Jan 29 14:39:43 UTC 2024
Mon Jan 29 14:39:45 UTC 2024
Mon Jan 29 14:39:47 UTC 2024
Mon Jan 29 14:39:49 UTC 2024
Mon Jan 29 14:39:51 UTC 2024
</pre>

<p>
	لاحظ أن ما نفذناه هنا يشبه فتح صدفة Shell تفاعلية (كما في المثال السابق <code>docker exec -it container-name sh</code>) ثم استدعاء الأمر <code>tail /var/log/date.log</code> لعرض النتائج، فقد حصلنا في الحالتين على الخرج نفسه، ولكننا في الحالة الأولى (أي التفاعلية) فتحنا الصدفة Shell، ثم نفذنا الأمر، وبعدها أغلقنا الصدفة، أما هنا فتمت العملية بأمرٍ واحد فقط وبدون الحاجة لفتح طرفية وهمية.
</p>

<h2 id="docker-2">
	تنفيذ الأوامر في مجلد بديل ضمن الحاوية Docker
</h2>

<p>
	يمكنك تحديد مجلد العمل أو المجلد الذي تود تنفيذ الأوامر فيه ضمن الحاوية بكتابة مساره بعد الراية <code>workdir--</code> كما في المثال التالي:
</p>

<pre class="ipsCode">$ docker exec --workdir /tmp container-name pwd
</pre>

<p>
	حددنا هنا المجلد <code>tmp/</code> على أنه مجلد العمل، وسيُنفذ ضمنه الأمر <code>pwd</code>، ووظيفة <code>pwd</code> عرض اسم مجلد العمل الحالي، وستحصل بذلك على الخرج التالي:
</p>

<pre class="ipsCode">/tmp
</pre>

<p>
	الذي يؤكد أن <code>tmp/</code> هو مجلد العمل الحالي الذي نُفِّذ فيه الأمر لأنه ظهر في خرج <code>pwd</code>.
</p>

<h2 id="docker-3">
	تشغيل الأوامر بصفة مستخدم آخر في حاوية Docker
</h2>

<p>
	استخدم الراية <code>user--</code> إذا رغبت بتنفيذ أمرٍ ما ضمن الحاوية بصفتك مستخدمًا آخر أي بصلاحياته والمحددات الخاصة به، كما في المثال التالي:
</p>

<pre class="ipsCode">$ docker exec --user guest container-name whoami
</pre>

<p>
	يُنفّذ السطر السابق الأمر <code>whoami</code> بصفة المستخدم <strong>guest</strong>، وبما أن الأمر <code>whoami</code> يستعمل عادةً لمعرفة هوية المستخدم الحالي فستحصل على هذا الخرج:
</p>

<pre class="ipsCode">guest
</pre>

<p>
	كما تلاحظ فقد أكدّ خرج الأمر <code>whoami</code> أن <strong>guest</strong> هو المستخدم المُنَفِذ.
</p>

<h2 id="docker-4">
	تمرير المتغيرات إلى داخل حاوية Docker
</h2>

<p>
	تساعدك الراية <code>e-</code> على تمرير قيم متغيرات البيئة environment variables إلى داخل الحاوية لتُستخدم عند تنفيذ الأمر، ألقِ نظرة على المثال التالي:
</p>

<pre class="ipsCode">$ docker exec -e TEST=sammy container-name env
</pre>

<p>
	يُسنِد هذا الأمر القيمة <code>sammy</code> إلى المتغير <code>TEST</code> ثم يُشغّل الأمر <code>env</code> في داخل الحاوية، علمًا أن وظيفة الأمر <code>env</code> هي عرض كافة متغيرات البيئة أمامك على الشاشة، مثل التالي:
</p>

<pre class="ipsCode">PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=76aded7112d4
TEST=sammy
HOME=/root
</pre>

<p>
	لاحظ أن المتغير <code>TEST</code> أصبح يحمل القيمة <code>sammy</code> بالفعل.
</p>

<p>
	أما إذا احتجت لتمرير عدة متغيرات فاكتبهم بالترتيب واكتب الراية <code>e-</code> قبل كل متغير منهم، وفق ما يلي:
</p>

<pre class="ipsCode">$ docker exec -e TEST=sammy -e ENVIRONMENT=prod container-name env
</pre>

<p>
	تستطيع أيضًا تمرير كافة متغيرات البيئة التي تحتاجها إلى الحاوية ضمن ملفٍ نصي يحتوي على قيمها، وذلك بواسطة الراية <code>env-file--</code>.
</p>

<p>
	لكن أول ما عليك فعله تجهيز ملف المتغيرات، لذا افتح أي <a href="https://academy.hsoub.com/programming/workflow/%D9%85%D8%AD%D8%B1%D8%B1%D8%A7%D8%AA-%D8%A7%D9%84%D9%86%D8%B5%D9%88%D8%B5-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D8%B9%D9%85%D9%84%D8%A9-%D9%81%D9%8A-%D8%AA%D8%B7%D9%88%D9%8A%D8%B1-%D9%85%D9%88%D8%A7%D9%82%D8%B9-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-r1438/" rel="">محرر نصوص</a> تفضله مثل <a href="https://academy.hsoub.com/programming/workflow/%D9%85%D9%82%D8%A7%D8%B1%D9%86%D8%A9-%D8%A8%D9%8A%D9%86-%D9%85%D8%AD%D8%B1%D8%B1-%D8%A7%D9%84%D9%86%D8%B5%D9%88%D8%B5-%D9%81%D9%8A%D9%85-vim-%D9%88%D9%86%D8%A7%D9%86%D9%88-nano-r1590/" rel="">نانو</a> <code>nano</code> أو غيره، وأنشئ بواسطته ملفًا جديدًا كما يلي:
</p>

<pre class="ipsCode">$ nano .env
</pre>

<p>
	اعتمدنا <code>env.</code> اسمًا لملف متغيرات البيئة، لأنه الاسم المتعارف عليه لهذا النوع من الملفات، والذي يعتمده معظم المطورين لتسمية ملفاتهم الخاصة بإدارة معلومات التطبيق الخارجة عن عمليات التحكم بالإصدار.
</p>

<p>
	عرّف الآن متغيرات بيئتك بكتابتها ضمن الملف بهيئة ثنائيات اسم وقيمة <code>KEY=value</code>، وبمعدل متغير واحد في كل سطر، كما في المثال أدناه:
</p>

<pre class="ipsCode">TEST=sammy
ENVIRONMENT=prod
</pre>

<p>
	احفظ التغييرات على الملف، وأغلقه، لننتقل للخطوة التالية. إذا كنت تستخدم محرر النصوص نانو <code>nano</code>، فاضغط على <code>CTRL+O</code> ثم <code>ENTER</code> لحفظ التغييرات، وبعدها اضغط على <code>CTRL+X</code> للخروج.
</p>

<p>
	نَفِّذ الآن الأمر <code>docker exec</code> بعد كتابة اسم ملف التغيرات بدقة بعد الراية <code>env-file--</code> كما يلي:
</p>

<pre class="ipsCode">$ docker exec --env-file .env container-name env
</pre>

<p>
	وسيظهر أمامك الخرج التالي:
</p>

<pre class="ipsCode">PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=76aded7112d4
TEST=sammy
ENVIRONMENT=prod
HOME=/root
</pre>

<p>
	لاحظ القيم التي حددناها للمتغيرين إنها مضبوطة تمامًا كما كتبناها ملف المتغيرات.
</p>

<p>
	يمكنك أيضًا تمرير عدة ملفات لمتغيرات البيئة مع تكرار كتابة الراية <code>env-file--</code> قبل كل واحد منها، وإذا حصل أي تعارض بينها في قيم المتغيرات، فإن الملف اللاحق سيكون ذا أولوية أعلى من الملفات التي تسبقه.
</p>

<h2 id="-2">
	أخطاء شائعة
</h2>

<p>
	ستواجه بعض الأخطاء الشائعة عند تعاملك مع الأمر <code>docker exec</code>، سنذكر أبرزها، انظر مثلًا الخطأ التالي:
</p>

<pre class="ipsCode">Error: No such container: container-name
</pre>

<p>
	يعني الخطأ <code>No such container</code> أن الحاوية التي تطلبها غير موجودة، أو أنك أخطأت في كتابة اسمها، يمكنك استخدام الأمر <code>docker ps</code> لتحري أسباب الخطأ والتحقق من صحة اسم الحاوية التي تريدها فهو يعرض أسماء جميع الحاويات المُشغلة على الخادم.
</p>

<p>
	وهذا خطأ آخر:
</p>

<pre class="ipsCode">Error response from daemon: Container 2a94aae70ea5dc92a12e30b13d0613dd6ca5919174d73e62e29cb0f79db6e4ab is not running
</pre>

<p>
	تعني رسالة <code>not running</code> أن الحاوية موجودة ولكنها متوقفة عن العمل، يمكنك تشغيلها بالأمر <code>docker start container-name</code>.
</p>

<p>
	أما الخطأ الأخير الذي نعرضه فهو:
</p>

<pre class="ipsCode">Error response from daemon: Container container-name is paused, unpause the container before exec
</pre>

<p>
	يشرح الخطأ نفسه بدقة، فالرسالة <code>Container is paused</code> تعني أن الحاوية موقفة عن العمل مؤقتًا، يمكنك إزالة الإيقاف المؤقت وإعادة تشغيلها باستخدام الأمر <code>docker unpause container-name</code>.
</p>

<h2 id="-3">
	الخلاصة
</h2>

<p>
	عرضنا في مقال اليوم طريقة تنفيذ الأوامر في حاويات Docker قيد التشغيل، ووضحنا أهم الخيارات المتاحة لتستخدمها حسب احتياجات عملك، ويمكنك معرفة المزيد عن هذه الحاويات والتعامل معها بالإطلاع على مقالات قسم <a href="https://academy.hsoub.com/devops/cloud-computing/docker/" rel="">Docker</a> على أكاديمية حسوب.
</p>

<p>
	ترجمة -وبتصرف- للمقال <a href="https://www.digitalocean.com/community/tutorials/how-to-use-docker-exec-to-run-commands-in-a-docker-container" rel="external nofollow">How To Use docker exec to Run Commands in a Docker Container</a> لصاحبه Brian Boucheron.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
	<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%AF%D9%88%D9%83%D8%B1-docker-r607/" rel="">مدخل إلى دوكر Docker</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D8%AA%D9%86%D8%B3%D9%8A%D9%82-%D8%A7%D9%84%D8%AD%D8%A7%D9%88%D9%8A%D8%A7%D8%AA-r643/" rel="">أساسيات تنسيق الحاويات</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D9%85%D8%A7-%D9%87%D9%8A-%D8%AA%D9%82%D9%86%D9%8A%D8%A9-docker%D8%9F-r639/" rel="">ما هي تقنية Docker؟</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D8%A7%D9%84%D8%A3%D8%AF%D8%A7%D8%A9-%D8%AF%D9%88%D9%83%D8%B1-%D9%83%D9%88%D9%85%D8%A8%D9%88%D8%B2-docker-compose-%D9%88%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85%D9%87%D8%A7-%D8%B6%D9%85%D9%86-%D9%86%D8%B8%D8%A7%D9%85-%D9%84%D9%8A%D9%86%D9%83%D8%B3-%D8%A3%D9%88%D8%A8%D9%88%D9%86%D8%AA%D9%88-r654/" rel="">تثبيت الأداة دوكر كومبوز واستخدامها ضمن نظام أوبونتو</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">813</guid><pubDate>Sun, 14 Jul 2024 15:03:01 +0000</pubDate></item><item><title>&#x62A;&#x634;&#x63A;&#x64A;&#x644; &#x62A;&#x637;&#x628;&#x64A;&#x642;&#x627;&#x62A; &#x628;&#x627;&#x64A;&#x62B;&#x648;&#x646; &#x62F;&#x627;&#x62E;&#x644; &#x62F;&#x648;&#x643;&#x631; &#x628;&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x645;&#x64A;&#x646;&#x64A;&#x643;&#x648;&#x646;&#x62F;&#x627; Miniconda</title><link>https://academy.hsoub.com/devops/cloud-computing/docker/%D8%AA%D8%B4%D8%BA%D9%8A%D9%84-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-%D8%AF%D8%A7%D8%AE%D9%84-%D8%AF%D9%88%D9%83%D8%B1-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%85%D9%8A%D9%86%D9%8A%D9%83%D9%88%D9%86%D8%AF%D8%A7-miniconda-r680/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_12/1218369329_------.jpg.2689473ce4b7215614aaa739f9f551ab.jpg" /></p>
<p>
	سننشئ في هذا المقال <a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D9%85%D8%A7-%D9%87%D9%8A-%D8%B5%D9%88%D8%B1%D8%A9-%D8%A7%D9%84%D8%AD%D8%A7%D9%88%D9%8A%D8%A9-container-image%D8%9F-r587/" rel="">صورة دوكر</a> مخصصة لتشغيل تطبيقات <a href="https://academy.hsoub.com/programming/python/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-r211/" rel="">بايثون</a> بداخلها، حيث يمكننا باستخدام ملف Dockerfile تخصيص وإنشاء صور دوكر جديدة، يمكن لمطوري التطبيقات أو المستخدمين أيضًا الاستفادة من ذلك، حيث سنستعين بصورة بايثون بسيطة وخفيفة، وبعد إجراء التعديلات على تلك الصورة ستصبح عملية تشغيل تطبيقات بايثون أسهل، ولن نقلق بشأن تشغيل تلك التطبيقات على <a href="https://academy.hsoub.com/files/24-%D8%A3%D9%86%D8%B8%D9%85%D8%A9-%D8%A7%D9%84%D8%AA%D8%B4%D8%BA%D9%8A%D9%84-%D9%84%D9%84%D9%85%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D9%86/" rel="">أنظمة التشغيل</a> المختلفة، حيث يمكن بتنفيذ أمر دوكر بسيط تشغيل تطبيق بايثون، ودون الحاجة للاستعانة ببرامج تثبيت التطبيقات التي توفرها الاستضافات عادةً. سنستخدم مينيكوندا Miniconda وهو مُثبّت كوندا مجاني يُنشئ gkh نسخة صغيرة ومجهزة من أناكوندا فيها الاحتياجات الأساسية لتطبيقات بايثون.
</p>

<h2>
	لماذا نستخدم مينيكوندا؟
</h2>

<p>
	لعدة أسباب حيث يستبدل مينيكوندا إصدار بايثون الذي يوفره مدير حزم نظام التشغيل، بإصدار بايثون يُثبته هو في مكان منفصل وله بيئته الخاصة، فنحصل على طبقة عزل إضافية عند استخدامه ضمن حاوية دوكر، ما يوفر لنا مزايا إضافية، فبما أننا نستخدم كوندا المُثبت من قبل مينيكوندا، يمكن استخدام تلك الأداة لتحديد إصدار بايثون الذي يحتاجه التطبيق، ما يفيد المطورين خصوصًا للعمل على عدة نسخ من بايثون معًا على نفس الجهاز، حيث يمكن مثلًا استخدام عدة إصدارات مختلفة من بايثون 3، مثل 3.6 أو 3.7 أو 3.8 أو 3.9 أو إصدارات أقدم منها، ومثلًا لو كان الإصدار الافتراضي الذي نستخدمه هو بايثون 3.9، ونعمل على تطبيق يحتاج لإصدار بايثون 3.7 واعتمادياته الخاصة، يمكن الاستفادة من كوندا لتغيير إصدار بايثون وتثبيت كافة اعتمادياته وذلك بتنفيذ الأمر <code>conda install python=3.7</code>.
</p>

<ul>
	<li>
		يسمح مينيكوندا بتثبيت تطبيقات لكل من بايثون 2 و بايثون 3، ومع أن إصدار بايثون 2 قد توقف دعمه لكن لا زال بإمكاننا اختبار التطبيقات القديمة باستخدامه دون الحاجة للتعديل عليها لدعم <a href="https://academy.hsoub.com/programming/python/%d8%a7%d9%84%d8%af%d9%84%d9%8a%d9%84-%d8%a7%d9%84%d8%b3%d8%b1%d9%8a%d8%b9-%d8%a5%d9%84%d9%89-%d9%84%d8%ba%d8%a9-%d8%a7%d9%84%d8%a8%d8%b1%d9%85%d8%ac%d8%a9-%d8%a8%d8%a7%d9%8a%d8%ab%d9%88%d9%86-python-3-r535/" rel="">إصدار بايثون 3</a> بأدوات مثل <a href="https://docs.python.org/3/library/2to3.html" rel="external nofollow">2to3</a>.
	</li>
	<li>
		أحيانًا تعتمد تطبيقات بايثون المُشغلة ضمن مينيكوندا على اعتماديات خارجية بغير لغة بايثون مثبتة على الجهاز المضيف، مثل الاعتماد على <code>g++‎</code>، وهنا تبرز قوة استخدام مينيكوندا ضمن دوكر كحل لتلك المشكلة.
	</li>
	<li>
		يمكن أيضًا إنشاء وتفعيل بيئات تطبيق بايثون مخصصة باستخدام كوندا، وذلك بالاعتماد على العزل.
	</li>
	<li>
		يمكن التبديل بين إصدار بايثون الافتراضي ضمن <a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D8%AD%D8%A7%D9%88%D9%8A%D8%A7%D8%AA-r641/" rel="">حاوية دوكر</a>، وبين الإصدار ضمن مينيكوندا في أي وقت، ما يوفر سهولة أكبر في إجراء التعديلات على التطبيق وإعادة بناء الصورة، والآن سنبدأ بإنشاء صورة دوكر جديدة لتطبيق بايثون مع مينيكوندا.
	</li>
</ul>

<h2>
	المستلزمات
</h2>

<p>
	أولًا يجب تثبيت دوكر بحسب <a href="https://academy.hsoub.com/devops/linux/%d8%a7%d9%84%d8%af%d9%84%d9%8a%d9%84-%d8%a7%d9%84%d9%86%d9%87%d8%a7%d8%a6%d9%8a-%d9%84%d8%a7%d8%ae%d8%aa%d9%8a%d8%a7%d8%b1-%d8%aa%d9%88%d8%b2%d9%8a%d8%b9%d8%a9-%d9%84%d9%8a%d9%86%d9%83%d8%b3-r48/" rel="">توزيعة لينكس</a> التي تعمل عليها، ولا ننسى إضافة المستخدم الخاص بنا إلى مجموعة دوكر حتى نتمكن من تنفيذ أوامر دوكر دون استخدام الأمر <code>sudo</code>، ويجب توفر اتصال بالإنترنت لنتمكن من تنزيل صورة دوكر الأساسية التي سنستخدمها.
</p>

<p>
	ولاختبار عملية تشغيل التطبيق باستخدام مينيكوندا داخل دوكر، سنستخدم تطبيق بسيط بالاسم python-app.py يطبع عبارة "Hello World!‎"، وبعدها يمكن تبديل ذلك التطبيق بتطبيق بايثون كامل يستخدم العديد من مكتبات بايثون، والاستفادة من بيئة مينيكوندا التي توفر عدة نسخ من الاعتماديات.
</p>

<h2>
	الخطوة الأولى: جلب صورة دوكر (اختياري)
</h2>

<p>
	سنستخدم صورة بايثون ذات الوسم slim بدلًا من وسم لينكس apline الخفيفة، لأنها تقدم أداء أفضل لتشغيل التطبيقات، وحجمها تقريبًا 40 ميجابايت مبنية على نسخة ديبيان Buster وإصدار بايثون 3.9.1، هذه الخطوة اختيارية والهدف منها توضيح الفرق بين استخدام نسخة جاهزة أو إنشاء نسخة مخصصة يدويًا باستخدام ملف دوكر Dockerfile، ويمكن تنزيل آخر إصدار من صورة بايثون slim باستخدام أمر <code>docker pull</code> كالتالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_462_11" style=""><span class="pln">docker pull python</span><span class="pun">:</span><span class="pln">slim</span></pre>

<h2>
	الخطوة الثانية: إنشاء ملف دوكر Dockerfile بالتخصيصات المطلوبة
</h2>

<p>
	ننشئ أولًا مجلد جديد لاحتواء تطبيق دوكر، ثم نُنشئ داخله ملف جديد فارغ ونسميه Dockerfile باستخدام الأمر <code>touch</code> كالتالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_462_13" style=""><span class="pln">mkdir python</span><span class="pun">-</span><span class="pln">docker
cd python</span><span class="pun">-</span><span class="pln">docker
touch </span><span class="typ">Dockerfile</span></pre>

<p>
	سنشرح خطوات عملية بناء صورة دوكر خطوة بخطوة، بعدها سنذكر المحتوى الكامل النهائي لملف Dockerfile.
</p>

<h3>
	تحضير الصورة الأساسية
</h3>

<p>
	التحديث إلى آخر إصدارات من الحزم الافتراضية باستخدام صورة بايثون slim:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_462_15" style=""><span class="pln">FROM python</span><span class="pun">:</span><span class="pln">slim
RUN apt</span><span class="pun">-</span><span class="pln">get update </span><span class="pun">&amp;&amp;</span><span class="pln"> apt</span><span class="pun">-</span><span class="pln">get </span><span class="pun">-</span><span class="pln">y upgrade \</span></pre>

<h3>
	تثبيت حزم الاعتماديات الخارجية
</h3>

<p>
	نثبت الاعتماديات الخارجية المكتوبة بغير لغة بايثون بحسب ما يحتاجه التطبيق، مثل <code>g++‎</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_462_17" style=""><span class="pln">  </span><span class="pun">&amp;&amp;</span><span class="pln"> apt</span><span class="pun">-</span><span class="pln">get install </span><span class="pun">-</span><span class="pln">y </span><span class="pun">--</span><span class="pln">no</span><span class="pun">-</span><span class="pln">install</span><span class="pun">-</span><span class="pln">recommends \
    git \
    wget \
    g</span><span class="pun">++</span><span class="pln"> \
    gcc \
    ca</span><span class="pun">-</span><span class="pln">certificates \
    </span><span class="pun">&amp;&amp;</span><span class="pln"> rm </span><span class="pun">-</span><span class="pln">rf </span><span class="pun">/</span><span class="pln">var</span><span class="pun">/</span><span class="pln">lib</span><span class="pun">/</span><span class="pln">apt</span><span class="pun">/</span><span class="pln">lists</span><span class="pun">/*</span></pre>

<p>
	يمكن الاستفادة من <a href="https://git-scm.com/" rel="external nofollow">Git</a> و <a href="https://www.gnu.org/software/wget/" rel="external nofollow">Wget</a> لجلب تطبيقات بايثون من المستودعات والعناوين المختلفة، أخيرًا يمكننا تقليص حجم صورة دوكر الناتجة عبر مسح قوائم الحزم باستخدام الأمر <code>rm -rf /var/lib/apt/lists/*‎</code>.
</p>

<h3>
	تثبيت مينيكوندا
</h3>

<p>
	يُعدل مينيكوندا بعد تثبيته الملف ‎.bashrc ليبدل إصدار بايثون المستخدم إلى الإصدار الخاص به:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_462_19" style=""><span class="pln">ENV PATH</span><span class="pun">=</span><span class="str">"/root/miniconda3/bin:${PATH}"</span><span class="pln">
ARG PATH</span><span class="pun">=</span><span class="str">"/root/miniconda3/bin:${PATH}"</span><span class="pln">
RUN wget https</span><span class="pun">://</span><span class="pln">repo</span><span class="pun">.</span><span class="pln">anaconda</span><span class="pun">.</span><span class="pln">com</span><span class="pun">/</span><span class="pln">miniconda</span><span class="pun">/</span><span class="typ">Miniconda3</span><span class="pun">-</span><span class="pln">latest</span><span class="pun">-</span><span class="typ">Linux</span><span class="pun">-</span><span class="pln">x86_64</span><span class="pun">.</span><span class="pln">sh \
    </span><span class="pun">&amp;&amp;</span><span class="pln"> mkdir </span><span class="pun">/</span><span class="pln">root</span><span class="pun">/.</span><span class="pln">conda \
    </span><span class="pun">&amp;&amp;</span><span class="pln"> bash </span><span class="typ">Miniconda3</span><span class="pun">-</span><span class="pln">latest</span><span class="pun">-</span><span class="typ">Linux</span><span class="pun">-</span><span class="pln">x86_64</span><span class="pun">.</span><span class="pln">sh </span><span class="pun">-</span><span class="pln">b \
    </span><span class="pun">&amp;&amp;</span><span class="pln"> rm </span><span class="pun">-</span><span class="pln">f </span><span class="typ">Miniconda3</span><span class="pun">-</span><span class="pln">latest</span><span class="pun">-</span><span class="typ">Linux</span><span class="pun">-</span><span class="pln">x86_64</span><span class="pun">.</span><span class="pln">sh \</span></pre>

<p>
	يٌعيّن متغير البيئة ضمن مسار الحاوية داخل النظام، حيث أن التعليمة <code>ENV</code> خاصة بالحاويات التي ستعمل وفقًا للصورة التي يتم بنائها، بينما <code>ARG</code> للاستخدام بين الحاويات الوسيطة التي تُنشئ خلال عملية بناء الصورة لأول مرة، فالفرق بين تعليمتي <code>ENV</code> و <code>ARG</code> في الشيفرة السابقة هو أن التعليمة الأخيرة متاحة خلال عملية بناء الصورة فقط، ونٌنزل باستخدام الأمر <code>wget</code> الإصدار الأخير من مينيكوندا من مستودع أناكوندا الرسمي، بعدها يتم إنشاء مجلد الضبط ثم تثبيته ثم حذف المُثبت.
</p>

<h3>
	إعداد مينيكوندا مع صدفة باش
</h3>

<p>
	بعد تثبيت مينيكوندا نعرض رقم الإصدار المثبت لتأكيد نجاح عملية التثبيت، ثم نُهيئه للعمل ضمن صدفة باش داخل الحاوية، السطر الثاني يُحدّث ملف <code>‎.bashrc</code> الافتراضي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_462_21" style=""><span class="pln">    </span><span class="pun">&amp;&amp;</span><span class="pln"> echo </span><span class="str">"Running $(conda --version)"</span><span class="pln"> </span><span class="pun">&amp;&amp;</span><span class="pln"> \
    conda init bash </span><span class="pun">&amp;&amp;</span><span class="pln"> \</span></pre>

<h3>
	إعادة تحميل باش لتطبيق التغييرات
</h3>

<p>
	نعيد تحميل باش داخل دوكر للتبديل من إصدار بايثون الافتراضي المثبت على نظام التشغيل الأساسي المٌستخدم في ديبيان، إلى إصدار بايثون الخاص بمينيكوندا:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_462_23" style=""><span class="pln">    </span><span class="pun">.</span><span class="pln"> </span><span class="pun">/</span><span class="pln">root</span><span class="pun">/.</span><span class="pln">bashrc </span><span class="pun">&amp;&amp;</span><span class="pln"> \</span></pre>

<p>
	ونحدث الحزم الافتراضية داخل حزمة مينيكوندا:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_462_26" style=""><span class="pln">    conda update conda </span><span class="pun">&amp;&amp;</span><span class="pln"> \</span></pre>

<h3>
	تحضير بيئة كوندا للتطبيق
</h3>

<p>
	نُنشئ ونُفعل بيئة كوندا منفصلة خاصة بتطبيق بايثون:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_462_28" style=""><span class="pln">    conda create </span><span class="pun">-</span><span class="pln">n python</span><span class="pun">-</span><span class="pln">app </span><span class="pun">&amp;&amp;</span><span class="pln"> \
    conda activate python</span><span class="pun">-</span><span class="pln">app </span><span class="pun">&amp;&amp;</span><span class="pln"> \</span></pre>

<p>
	نثبت إصدار بايثون المطلوب من قبل التطبيق، وذلك بفرض أن التطبيق مطور ليعمل على إصدار بايثون 3.6 نعيّن ذلك الإصدار ضمن البيئة الافتراضية الجديدة، إضافة إلى مدير الحزم <a href="https://pypi.org/project/pip/" rel="external nofollow">Pip</a> ليساعدنا في إدارة تطبيقات بايثون:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_462_30" style=""><span class="pln">    conda install python</span><span class="pun">=</span><span class="lit">3.6</span><span class="pln"> pip </span><span class="pun">&amp;&amp;</span><span class="pln"> \</span></pre>

<h3>
	تثبيت تطبيق بايثون
</h3>

<p>
	بحسب طريقة استخدام التطبيق، يمكن تنفيذ أحد أمرين، الأول تثبيته باستخدام pip والذي بدوره يستدعي الملف <code>setup.py</code> ضمن مستودع التطبيق:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_462_32" style=""><span class="pln">    git clone replace</span><span class="pun">-</span><span class="pln">me</span><span class="pun">-</span><span class="kwd">with</span><span class="pun">-</span><span class="pln">repo</span><span class="pun">-</span><span class="pln">url
    cd repo</span><span class="pun">-</span><span class="pln">name
    pip install </span><span class="pun">-</span><span class="pln">e </span><span class="pun">.</span></pre>

<p>
	الثاني تنفيذه مباشرة باستخدام الأمر <code>python</code>:
</p>

<pre class="ipsCode">    git clone replace-me-with-repo-url
    cd repo-name
    python python-app.py
</pre>

<p>
	لتجربة العملية بشكل كامل وفهم كيف يمكن تشغيل التطبيق مباشرةً عبر تشغيل حاوية أو داخل صدفة باش باستخدام دوكر، سنستخدم التطبيق المثال الذي ذكرناه سابقًا python-app.py، باتباع الطريقة الثانية ننشئ ملف التطبيق python-app.py:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_462_34" style=""><span class="pln">    echo </span><span class="str">'print("Hello World!")'</span><span class="pln"> </span><span class="pun">&gt;</span><span class="pln"> python</span><span class="pun">-</span><span class="pln">app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	الملف السابق سيطبع عبارة "Hello World!‎" عند تنفيذه باستخدام الأمر<code>python python-app.py</code>.
</p>

<h3>
	تحديث ملف ‎.bashrc للتطبيق مثل مينيكوندا
</h3>

<p>
	كما ذكرنا سابقًا، يُحدث مثبت مينيكوندا ملف ‎.bashrc تلقائيًا بعد تنفيذ الأمر <code>conda init bash</code>، يمكننا تنفيذ أمر مشابه لتطبيقنا، بحيث كلما دخلنا إلى <a href="https://academy.hsoub.com/devops/servers/%D9%85%D8%A7-%D9%87%D9%88-%D8%B3%D8%B7%D8%B1-%D8%A7%D9%84%D8%A3%D9%88%D8%A7%D9%85%D8%B1-%D8%9F-r353/" rel="">سطر أوامر</a> باش داخل الحاوية سيتم تفعيل البيئة، ويمكن استخدام اسم التطبيق كأمر تشغيل له، سنستخدم الاسم <code>python-app</code> للدلالة على التطبيق:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_462_37" style=""><span class="pln">RUN echo </span><span class="str">'conda activate python-app \n\
alias python-app="python python-app.py"'</span><span class="pln"> </span><span class="pun">&gt;&gt;</span><span class="pln"> </span><span class="pun">/</span><span class="pln">root</span><span class="pun">/.</span><span class="pln">bashrc</span></pre>

<h3>
	تحضير التطبيق للتنفيذ النهائي
</h3>

<p>
	أخيرًا نٌنشئ المدخل للتطبيق ونعين له الأمر الذي سيسمح لنا بتشغيله في كل مرة نشغل حاوية مبنية من الصورة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_462_39" style=""><span class="pln">ENTRYPOINT </span><span class="pun">[</span><span class="pln"> </span><span class="str">"/bin/bash"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"-l"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"-c"</span><span class="pln"> </span><span class="pun">]</span><span class="pln">
CMD </span><span class="pun">[</span><span class="str">"python python-app.py"</span><span class="pun">]</span></pre>

<h3>
	ملف Dockerfile النهائي
</h3>

<p>
	باستخدام برنامج محرر نصوص مثل <a href="https://academy.hsoub.com/programming/workflow/%D9%85%D9%82%D8%A7%D8%B1%D9%86%D8%A9-%D8%A8%D9%8A%D9%86-%D9%85%D8%AD%D8%B1%D8%B1-%D8%A7%D9%84%D9%86%D8%B5%D9%88%D8%B5-%D9%81%D9%8A%D9%85-vim-%D9%88%D9%86%D8%A7%D9%86%D9%88-nano-r1590/" rel="">Vim أو Nano</a> أو باستخدام الأمر <code>cat</code> نضيف الأسطر التي شرحناها سابقًا إلى ملف Dockerfile:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_462_41" style=""><span class="pln">FROM python</span><span class="pun">:</span><span class="pln">slim
RUN apt</span><span class="pun">-</span><span class="pln">get update </span><span class="pun">&amp;&amp;</span><span class="pln"> apt</span><span class="pun">-</span><span class="pln">get </span><span class="pun">-</span><span class="pln">y upgrade \
  </span><span class="pun">&amp;&amp;</span><span class="pln"> apt</span><span class="pun">-</span><span class="pln">get install </span><span class="pun">-</span><span class="pln">y </span><span class="pun">--</span><span class="pln">no</span><span class="pun">-</span><span class="pln">install</span><span class="pun">-</span><span class="pln">recommends \
    git \
    wget \
    g</span><span class="pun">++</span><span class="pln"> \
    ca</span><span class="pun">-</span><span class="pln">certificates \
    </span><span class="pun">&amp;&amp;</span><span class="pln"> rm </span><span class="pun">-</span><span class="pln">rf </span><span class="pun">/</span><span class="pln">var</span><span class="pun">/</span><span class="pln">lib</span><span class="pun">/</span><span class="pln">apt</span><span class="pun">/</span><span class="pln">lists</span><span class="pun">/*</span><span class="pln">
ENV PATH</span><span class="pun">=</span><span class="str">"/root/miniconda3/bin:${PATH}"</span><span class="pln">
ARG PATH</span><span class="pun">=</span><span class="str">"/root/miniconda3/bin:${PATH}"</span><span class="pln">
RUN wget https</span><span class="pun">://</span><span class="pln">repo</span><span class="pun">.</span><span class="pln">anaconda</span><span class="pun">.</span><span class="pln">com</span><span class="pun">/</span><span class="pln">miniconda</span><span class="pun">/</span><span class="typ">Miniconda3</span><span class="pun">-</span><span class="pln">latest</span><span class="pun">-</span><span class="typ">Linux</span><span class="pun">-</span><span class="pln">x86_64</span><span class="pun">.</span><span class="pln">sh \
    </span><span class="pun">&amp;&amp;</span><span class="pln"> mkdir </span><span class="pun">/</span><span class="pln">root</span><span class="pun">/.</span><span class="pln">conda \
    </span><span class="pun">&amp;&amp;</span><span class="pln"> bash </span><span class="typ">Miniconda3</span><span class="pun">-</span><span class="pln">latest</span><span class="pun">-</span><span class="typ">Linux</span><span class="pun">-</span><span class="pln">x86_64</span><span class="pun">.</span><span class="pln">sh </span><span class="pun">-</span><span class="pln">b \
    </span><span class="pun">&amp;&amp;</span><span class="pln"> rm </span><span class="pun">-</span><span class="pln">f </span><span class="typ">Miniconda3</span><span class="pun">-</span><span class="pln">latest</span><span class="pun">-</span><span class="typ">Linux</span><span class="pun">-</span><span class="pln">x86_64</span><span class="pun">.</span><span class="pln">sh \
    </span><span class="pun">&amp;&amp;</span><span class="pln"> echo </span><span class="str">"Running $(conda --version)"</span><span class="pln"> </span><span class="pun">&amp;&amp;</span><span class="pln"> \
    conda init bash </span><span class="pun">&amp;&amp;</span><span class="pln"> \
    </span><span class="pun">.</span><span class="pln"> </span><span class="pun">/</span><span class="pln">root</span><span class="pun">/.</span><span class="pln">bashrc </span><span class="pun">&amp;&amp;</span><span class="pln"> \
    conda update conda </span><span class="pun">&amp;&amp;</span><span class="pln"> \
    conda create </span><span class="pun">-</span><span class="pln">n python</span><span class="pun">-</span><span class="pln">app </span><span class="pun">&amp;&amp;</span><span class="pln"> \
    conda activate python</span><span class="pun">-</span><span class="pln">app </span><span class="pun">&amp;&amp;</span><span class="pln"> \
    conda install python</span><span class="pun">=</span><span class="lit">3.6</span><span class="pln"> pip </span><span class="pun">&amp;&amp;</span><span class="pln"> \
    echo </span><span class="str">'print("Hello World!")'</span><span class="pln"> </span><span class="pun">&gt;</span><span class="pln"> python</span><span class="pun">-</span><span class="pln">app</span><span class="pun">.</span><span class="pln">py
RUN echo </span><span class="str">'conda activate python-app \n\
alias python-app="python python-app.py"'</span><span class="pln"> </span><span class="pun">&gt;&gt;</span><span class="pln"> </span><span class="pun">/</span><span class="pln">root</span><span class="pun">/.</span><span class="pln">bashrc
ENTRYPOINT </span><span class="pun">[</span><span class="pln"> </span><span class="str">"/bin/bash"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"-l"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"-c"</span><span class="pln"> </span><span class="pun">]</span><span class="pln">
CMD </span><span class="pun">[</span><span class="str">"python python-app.py"</span><span class="pun">]</span></pre>

<p>
	لتشغيل تطبيق مختلف ضمن الحاوية، نبدل السطر التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_462_43" style=""><span class="pln">echo </span><span class="str">'print("Hello World!")'</span><span class="pln"> </span><span class="pun">&gt;</span><span class="pln"> python</span><span class="pun">-</span><span class="pln">app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	بإحدى الطريقتين المذكورتين سابقًا ضمن قسم "تثبيت تطبيق بايثون".
</p>

<h2>
	الخطوة الثالثة: بناء صورة تطبيق بايثون اعتمادا على ملف Dockerfile
</h2>

<p>
	أمر بناء صورة معدلة اعتمادًا على ملف Dockerfile هو كالتالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_462_45" style=""><span class="pln">docker build </span><span class="pun">-</span><span class="pln">t python</span><span class="pun">-</span><span class="pln">app PATH_to_Dockerfile</span></pre>

<p>
	الخيار <code>‎-t</code> يُمكننا من وسم الصورة الناتجة باسم تطبيقنا، بحيث عينا له القيمة <code>python-app</code> في الأمر السابق،
</p>

<p>
	بفرض أن ملف Dockerfile موجود ضمن المجلد الحالي يمكننا إنشاء صورة دوكر جديدة لتطبيق بايثون كالتالي:
</p>

<pre class="ipsCode">docker build -t python-app .
</pre>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_462_47" style=""><span class="pln">avimanyu@iborg</span><span class="pun">-</span><span class="pln">desktop</span><span class="pun">:~/</span><span class="pln">python</span><span class="pun">-</span><span class="pln">docker$ docker build </span><span class="pun">-</span><span class="pln">t python</span><span class="pun">-</span><span class="pln">app </span><span class="pun">.</span><span class="pln">
</span><span class="typ">Sending</span><span class="pln"> build context to </span><span class="typ">Docker</span><span class="pln"> daemon   </span><span class="lit">2.56kB</span><span class="pln">
</span><span class="typ">Step</span><span class="pln"> </span><span class="lit">1</span><span class="pun">/</span><span class="lit">8</span><span class="pln"> </span><span class="pun">:</span><span class="pln"> FROM python</span><span class="pun">:</span><span class="pln">slim
 </span><span class="pun">---&gt;</span><span class="pln"> </span><span class="lit">677f7ac99e48</span><span class="pln">
</span><span class="typ">Step</span><span class="pln"> </span><span class="lit">2</span><span class="pun">/</span><span class="lit">8</span><span class="pln"> </span><span class="pun">:</span><span class="pln"> RUN apt</span><span class="pun">-</span><span class="pln">get update </span><span class="pun">&amp;&amp;</span><span class="pln"> apt</span><span class="pun">-</span><span class="pln">get </span><span class="pun">-</span><span class="pln">y upgrade   </span><span class="pun">&amp;&amp;</span><span class="pln"> apt</span><span class="pun">-</span><span class="pln">get install </span><span class="pun">-</span><span class="pln">y </span><span class="pun">--</span><span class="pln">no</span><span class="pun">-</span><span class="pln">install</span><span class="pun">-</span><span class="pln">recommends     git     wget     g</span><span class="pun">++</span><span class="pln">     ca</span><span class="pun">-</span><span class="pln">certificates     </span><span class="pun">&amp;&amp;</span><span class="pln"> rm </span><span class="pun">-</span><span class="pln">rf </span><span class="pun">/</span><span class="pln">var</span><span class="pun">/</span><span class="pln">lib</span><span class="pun">/</span><span class="pln">apt</span><span class="pun">/</span><span class="pln">lists</span><span class="pun">/*</span><span class="pln">
 </span><span class="pun">---&gt;</span><span class="pln"> </span><span class="typ">Using</span><span class="pln"> cache
 </span><span class="pun">---&gt;</span><span class="pln"> </span><span class="lit">15ee9c47c83b</span><span class="pln">
</span><span class="typ">Step</span><span class="pln"> </span><span class="lit">3</span><span class="pun">/</span><span class="lit">8</span><span class="pln"> </span><span class="pun">:</span><span class="pln"> ENV PATH</span><span class="pun">=</span><span class="str">"/root/miniconda3/bin:${PATH}"</span><span class="pln">
 </span><span class="pun">---&gt;</span><span class="pln"> </span><span class="typ">Using</span><span class="pln"> cache
 </span><span class="pun">---&gt;</span><span class="pln"> cfd5ed6b5ec9
</span><span class="typ">Step</span><span class="pln"> </span><span class="lit">4</span><span class="pun">/</span><span class="lit">8</span><span class="pln"> </span><span class="pun">:</span><span class="pln"> ARG PATH</span><span class="pun">=</span><span class="str">"/root/miniconda3/bin:${PATH}"</span><span class="pln">
 </span><span class="pun">---&gt;</span><span class="pln"> </span><span class="typ">Using</span><span class="pln"> cache
 </span><span class="pun">---&gt;</span><span class="pln"> e70d06b5ff10
</span><span class="typ">Step</span><span class="pln"> </span><span class="lit">5</span><span class="pun">/</span><span class="lit">8</span><span class="pln"> </span><span class="pun">:</span><span class="pln"> RUN wget https</span><span class="pun">://</span><span class="pln">repo</span><span class="pun">.</span><span class="pln">anaconda</span><span class="pun">.</span><span class="pln">com</span><span class="pun">/</span><span class="pln">miniconda</span><span class="pun">/</span><span class="typ">Miniconda3</span><span class="pun">-</span><span class="pln">latest</span><span class="pun">-</span><span class="typ">Linux</span><span class="pun">-</span><span class="pln">x86_64</span><span class="pun">.</span><span class="pln">sh     </span><span class="pun">&amp;&amp;</span><span class="pln"> mkdir </span><span class="pun">/</span><span class="pln">root</span><span class="pun">/.</span><span class="pln">conda     </span><span class="pun">&amp;&amp;</span><span class="pln"> bash </span><span class="typ">Miniconda3</span><span class="pun">-</span><span class="pln">latest</span><span class="pun">-</span><span class="typ">Linux</span><span class="pun">-</span><span class="pln">x86_64</span><span class="pun">.</span><span class="pln">sh </span><span class="pun">-</span><span class="pln">b     </span><span class="pun">&amp;&amp;</span><span class="pln"> rm </span><span class="pun">-</span><span class="pln">f </span><span class="typ">Miniconda3</span><span class="pun">-</span><span class="pln">latest</span><span class="pun">-</span><span class="typ">Linux</span><span class="pun">-</span><span class="pln">x86_64</span><span class="pun">.</span><span class="pln">sh     </span><span class="pun">&amp;&amp;</span><span class="pln"> echo </span><span class="str">"Running $(conda --version)"</span><span class="pln"> </span><span class="pun">&amp;&amp;</span><span class="pln">     conda init bash </span><span class="pun">&amp;&amp;</span><span class="pln">     </span><span class="pun">.</span><span class="pln"> </span><span class="pun">/</span><span class="pln">root</span><span class="pun">/.</span><span class="pln">bashrc </span><span class="pun">&amp;&amp;</span><span class="pln">     conda update conda </span><span class="pun">&amp;&amp;</span><span class="pln">     conda create </span><span class="pun">-</span><span class="pln">n python</span><span class="pun">-</span><span class="pln">app </span><span class="pun">&amp;&amp;</span><span class="pln">     conda activate python</span><span class="pun">-</span><span class="pln">app </span><span class="pun">&amp;&amp;</span><span class="pln">     conda install python</span><span class="pun">=</span><span class="lit">3.6</span><span class="pln"> pip </span><span class="pun">&amp;&amp;</span><span class="pln">     echo </span><span class="str">'print("Hello World!")'</span><span class="pln"> </span><span class="pun">&gt;</span><span class="pln"> python</span><span class="pun">-</span><span class="pln">app</span><span class="pun">.</span><span class="pln">py
 </span><span class="pun">---&gt;</span><span class="pln"> </span><span class="typ">Using</span><span class="pln"> cache
 </span><span class="pun">---&gt;</span><span class="pln"> </span><span class="lit">8a7957a6abb2</span><span class="pln">
</span><span class="typ">Step</span><span class="pln"> </span><span class="lit">6</span><span class="pun">/</span><span class="lit">8</span><span class="pln"> </span><span class="pun">:</span><span class="pln"> RUN echo </span><span class="str">'conda activate python-app \nalias python-app="python python-app.py"'</span><span class="pln"> </span><span class="pun">&gt;&gt;</span><span class="pln"> </span><span class="pun">/</span><span class="pln">root</span><span class="pun">/.</span><span class="pln">bashrc
 </span><span class="pun">---&gt;</span><span class="pln"> </span><span class="typ">Running</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> e3193e93b631
</span><span class="typ">Removing</span><span class="pln"> intermediate container e3193e93b631
 </span><span class="pun">---&gt;</span><span class="pln"> </span><span class="lit">948f45eb6024</span><span class="pln">
</span><span class="typ">Step</span><span class="pln"> </span><span class="lit">7</span><span class="pun">/</span><span class="lit">8</span><span class="pln"> </span><span class="pun">:</span><span class="pln"> ENTRYPOINT </span><span class="pun">[</span><span class="pln"> </span><span class="str">"/bin/bash"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"-l"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"-c"</span><span class="pln"> </span><span class="pun">]</span><span class="pln">
 </span><span class="pun">---&gt;</span><span class="pln"> </span><span class="typ">Running</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> </span><span class="lit">621624951dcf</span><span class="pln">
</span><span class="typ">Removing</span><span class="pln"> intermediate container </span><span class="lit">621624951dcf</span><span class="pln">
 </span><span class="pun">---&gt;</span><span class="pln"> </span><span class="lit">6e8824889502</span><span class="pln">
</span><span class="typ">Step</span><span class="pln"> </span><span class="lit">8</span><span class="pun">/</span><span class="lit">8</span><span class="pln"> </span><span class="pun">:</span><span class="pln"> CMD </span><span class="pun">[</span><span class="str">"python python-app.py"</span><span class="pun">]</span><span class="pln">
 </span><span class="pun">---&gt;</span><span class="pln"> </span><span class="typ">Running</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> dc97f9d0d8fe
</span><span class="typ">Removing</span><span class="pln"> intermediate container dc97f9d0d8fe
 </span><span class="pun">---&gt;</span><span class="pln"> </span><span class="lit">01bae0a9903c</span><span class="pln">
</span><span class="typ">Successfully</span><span class="pln"> built </span><span class="lit">01bae0a9903c</span><span class="pln">
</span><span class="typ">Successfully</span><span class="pln"> tagged python</span><span class="pun">-</span><span class="pln">app</span><span class="pun">:</span><span class="pln">latest</span></pre>

<p>
	يعتمد خرج الأمر السابق على البيانات الموجودة ضمن التخزين المؤقت، بحيث سيأخذ مدة أطول عند تنفيذه لأول مرة وسينتج خرج أطول، يمكننا التحقق فيما إذا كانت صورة دوكر الناتجة تحوي التطبيق التجريبي الذي ثبتناه عبر تشغيل حاوية من تلك الصورة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_462_49" style=""><span class="pln">docker run python</span><span class="pun">-</span><span class="pln">app
avimanyu@iborg</span><span class="pun">-</span><span class="pln">desktop</span><span class="pun">:~/</span><span class="pln">python</span><span class="pun">-</span><span class="pln">docker$ docker run python</span><span class="pun">-</span><span class="pln">app
</span><span class="typ">Hello</span><span class="pln"> </span><span class="typ">World</span><span class="pun">!</span></pre>

<p>
	باستخدام دوكر ومينيكوندا يمكننا الآن تشغيل التطبيق مباشرة بدون أي عملية تثبيت أو تهيئة مسبقة للبيئة، حيث كل ما نريده هو تلك الصورة الناتجة، ويمكننا الآن تسجيل الدخول إلى صدفة باش داخل الحاوية كالتالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_462_51" style=""><span class="pln">docker run </span><span class="pun">-</span><span class="pln">ti python</span><span class="pun">-</span><span class="pln">app bash
avimanyu@iborg</span><span class="pun">-</span><span class="pln">desktop</span><span class="pun">:~/</span><span class="pln">python</span><span class="pun">-</span><span class="pln">docker$ docker run </span><span class="pun">-</span><span class="pln">ti python</span><span class="pun">-</span><span class="pln">app bash
</span><span class="pun">(</span><span class="pln">python</span><span class="pun">-</span><span class="pln">app</span><span class="pun">)</span><span class="pln"> root@2ceec4c9eaa4</span><span class="pun">:/#</span><span class="pln"> </span></pre>

<p>
	وكما نلاحظ دخلنا إلى بيئة كوندا المفعلة التي أنشأناها سابقًا عبر ملف Dockerfile، استخدمنا الخيار <code>‎-ti</code> لإنشاء طرفية تفاعلية لاستخدامها، يمكننا أيضًا استخدام الاسم بديل الذي عيناه للتطبيق سابقًا لتشغيله كالتالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_462_53" style=""><span class="pun">(</span><span class="pln">python</span><span class="pun">-</span><span class="pln">app</span><span class="pun">)</span><span class="pln"> root@2ceec4c9eaa4</span><span class="pun">:/#</span><span class="pln"> python</span><span class="pun">-</span><span class="pln">app
</span><span class="typ">Hello</span><span class="pln"> </span><span class="typ">World</span><span class="pun">!</span></pre>

<p>
	لنتحقق أيضًا من استخدامنا لإصدار بايثون الخاص بمينيكوندا وليس الإصدار الافتراضي الخاص بنظام التشغيل:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_462_55" style=""><span class="pun">(</span><span class="pln">python</span><span class="pun">-</span><span class="pln">app</span><span class="pun">)</span><span class="pln"> root@2ceec4c9eaa4</span><span class="pun">:/#</span><span class="pln"> python </span><span class="pun">--</span><span class="pln">version
</span><span class="typ">Python</span><span class="pln"> </span><span class="lit">3.6</span><span class="pun">.</span><span class="lit">12</span><span class="pln"> </span><span class="pun">::</span><span class="pln"> </span><span class="typ">Anaconda</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Inc</span><span class="pun">.</span></pre>

<p>
	كما ذكرنا سابقًا مينيكوندا هو نسخة مصغرة من أناكوندا، وبعد الانتهاء من التحضير بشكل كامل يمكننا دفع الصورة النهائية للتطبيق إلى <a href="https://hub.docker.com/" rel="external nofollow">Docker Hub</a> في حال كان التطبيق نسخة مفتوحة المصدر ومُستضافة على أحد استضافات المستودعات مثل <a href="https://github.com/" rel="external nofollow">GitHub</a> أو <a href="https://gitlab.com/" rel="external nofollow">GitLab</a> أو <a href="https://gitea.io/" rel="external nofollow">Gitea</a> أو <a href="https://bitbucket.org/" rel="external nofollow">Bitbucket</a> أو أي مستودع آخر.
</p>

<p>
	يمكن الخروج من الحاوية بتنفيذ الأمر <code>exit</code> ضمن الطرفية، لتوفير المساحة المستخدمة يمكننا إيقاف الحاوية وحذفها وحذف صور دوكر التي تم بناؤها خلال العملية.
</p>

<h2>
	ختامًا
</h2>

<p>
	تعلمنا في هذا المقال كيف يمكن إنشاء صورة دوكر مخصصة لتشغيل تطبيق بايثون، يزيد مينيكوندا من مرونة تطبيقات بايثون ويُسهل عملية تحديثها مستقبلًا، كما يسهل عمل المطور كثيرًا، حيث يمكن بنفس الطريقة إعداده مع PyCharm.
</p>

<p>
	قمنا بتثبيت مينيكوندا وتطبيق بايثون الذي نعمل عليه كما هي العادة، الفرق أن عملية بناء وحفظ الصورة يتم لمرة واحدة فقط، وبعدها يصبح الأمر عبارة عن عملية واحدة وهي تشغيل حاوية التطبيق فقط.
</p>

<p>
	ترجمة -وبتصرف- للمقال <a href="https://linuxhandbook.com/dockerize-python-apps/" rel="external nofollow">How to Dockerize Python Applications With Miniconda [A Hybrid Approach]</a> لصاحبه Avimanyu Bandyopadhyay.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
	<li>
		<a href="https://academy.hsoub.com/programming/python/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-r211/" rel="">مدخل إلى لغة بايثون البرمجية</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%AF%D9%88%D9%83%D8%B1-docker-r607/" rel="">مدخل إلى دوكر Docker</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D8%AF%D9%88%D9%83%D8%B1-%D9%88%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85%D9%87-%D8%B9%D9%84%D9%89-%D8%AF%D8%A8%D9%8A%D8%A7%D9%86-r465/" rel="">كيفية تثبيت دوكر واستخدامه على دبيان</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D9%85%D9%82%D8%AF%D9%91%D9%85%D8%A9-%D8%B9%D9%86-%D8%A7%D9%84%D9%85%D9%8F%D9%83%D9%88%D9%91%D9%86%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D9%8F%D8%B4%D8%AA%D8%B1%D9%8E%D9%83%D8%A9-%D9%81%D9%8A-docker-r21/" rel="">مقدّمة عن المُكوّنات المُشترَكة في Docker</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">680</guid><pubDate>Tue, 31 Jan 2023 17:00:00 +0000</pubDate></item><item><title>&#x62A;&#x648;&#x633;&#x64A;&#x639; &#x62A;&#x637;&#x628;&#x64A;&#x642; &#x62C;&#x627;&#x646;&#x63A;&#x648; &#x648;&#x62A;&#x623;&#x645;&#x64A;&#x646;&#x647; &#x639;&#x628;&#x631; &#x62D;&#x627;&#x648;&#x64A;&#x629; &#x62F;&#x648;&#x643;&#x631; &#x648;&#x62E;&#x627;&#x62F;&#x645; Nginx &#x648;&#x62E;&#x62F;&#x645;&#x629; Let's Encrypt</title><link>https://academy.hsoub.com/devops/cloud-computing/docker/%D8%AA%D9%88%D8%B3%D9%8A%D8%B9-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D9%88%D8%AA%D8%A3%D9%85%D9%8A%D9%86%D9%87-%D8%B9%D8%A8%D8%B1-%D8%AD%D8%A7%D9%88%D9%8A%D8%A9-%D8%AF%D9%88%D9%83%D8%B1-%D9%88%D8%AE%D8%A7%D8%AF%D9%85-nginx-%D9%88%D8%AE%D8%AF%D9%85%D8%A9-lets-encrypt-r659/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_11/636752c059b91_------Nginx--Lets-Encrypt.jpg.2a604da9cb01f093331536f6fec90a96.jpg" /></p>

<p>
	توفر البيئة السحابية حلولًا مرنة لتوسيع التطبيقات العاملة ضمنها مثل <a href="https://academy.hsoub.com/programming/python/django/" rel="">جانغو Django</a> وتأمينها، ومن أبرز هذه الحلول التوسع الأفقي بزيادة عدد خوادم التطبيق وعملها معًا لتحقيق إتاحة عالية للخدمة تخفف من فترات التوقف الناجمة عن الأعطال أو الضغط أو غيرها، وزيادة الإنتاجية وسعة المعالجة، واستخدام وكيل عكسي يوجه الطلبات الخارجية إلى خوادم التطبيق.
</p>

<p>
	لجهة التوسع الأفقي يمكنك استخدام أكثر من خادم تطبيق لأداء مهمة تشغيل جانغو مع خادم WSGI HTTP الخاص به (وهو في الغالب <a href="https://gunicorn.org/" rel="external nofollow">Gunicorn</a> أو <a href="https://uwsgi-docs.readthedocs.io/en/latest/" rel="external nofollow">uWSGI</a>)، وحول توجيه الحركة يمكنك إعداد خادم خاص يوزع الطلبات بين خوادم التطبيق مثل <a href="https://academy.hsoub.com/devops/servers/web/nginx/" rel="">Nginx</a> فهو قادر على القيام بدور <a href="https://academy.hsoub.com/devops/servers/web/nginx/%D9%83%D9%8A%D9%81%D9%8A%D9%91%D8%A9-%D8%B6%D8%A8%D8%B7-nginx-%D9%84%D9%84%D8%B9%D9%85%D9%84-%D9%83%D8%AE%D8%A7%D8%AF%D9%85-%D9%88%D9%8A%D8%A8-%D9%88%D9%83%D9%88%D8%B3%D9%8A%D8%B7-%D8%B9%D9%83%D8%B3%D9%8A-%D9%84%D9%80-apache-%D8%B9%D9%84%D9%89-%D8%AE%D8%A7%D8%AF%D9%85-ubuntu-1804-r396/" rel="">الوكيل العكسي</a> reverse proxying و موازن الحمل load balancing، بالإضافة إلى تأمين التخزين المؤقت Cache لمكونات وأصول التطبيق الساكنة static assets، علاوةً على أنه يدعم بروتوكول أمان طبقة النقل <abbr title="Transport Layer Security | بروتوكول أمن طبقة النقل">TLS</abbr> لتكون كافة اتصالات التطبيق آمنة عبر HTTPS.
</p>

<p>
	أما <a href="https://academy.hsoub.com/devops/cloud-computing/docker/" rel="">حاويات دوكر</a> المستخدمة في حالتنا فهي تتميز بسهولة تثبيت الحزم وإجراء الإعدادات للمكونات الموجودة داخلها (مثل جانغو وخادم Nginx) كما أنها تضمن للمطوّر أن هذه المكونات التي أعدّها ستعمل دائمًا بنفس الأسلوب بصرف النظر عن البيئة التي تُبنى فيها.
</p>

<p>
	ستتعلم في هذا المقال كيفية توسيع تطبيق جانغو <a href="https://docs.djangoproject.com/en/3.0/intro/tutorial01/" rel="external nofollow">Polls</a> -هو اسم التطبيق- المغلف في حاوية توسيعًا أفقيًا بإعداد خادمين للتطبيق يُشغل كل منهما حاوية تتضمن جانغو وخادم Gunicorn، وكيفية تأمينه بتفعيل HTTPS وإعداد خادم ثالث يؤدي وظيفة الوكيل العكسي ويُشغل حاويتين واحدة تتضمن Nginx والأخرى تتضمن عميل <a href="https://certbot.eff.org/" rel="external nofollow">Certbot</a> الذي سيوفر لخادم Nginx شهادات <abbr title="Transport Layer Security | بروتوكول أمن طبقة النقل">TLS</abbr> مصدقة من <a href="https://academy.hsoub.com/devops/servers/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-%D8%AE%D8%AF%D9%85%D8%A9-let%E2%80%99s-encrypt-r352//" rel="">Let’s Encrypt</a> وقادرة على منح تطبيقك تقييم عالي بنتائج اختبار <a href="https://www.ssllabs.com/" rel="external nofollow"><abbr title="Secure Socket Layer | طبقة المنافذ الآمنة">SSL</abbr> Labs</a>، سيتلقى هذا الوكيل العكسي كافة الطلبات الخارجية الواردة ويحولها إلى خادمي تطبيق جانغو للإجابة عليها، وزيادة في الأمان سنقيّد الاتصال الخارجي بحيث يتم عبر الوكيل العكسي حصرًا.
</p>

<p>
	هذا المقال جزء من سلسلة قصيرة حول احتواء تطبيقات جانغو بحاويات وإدارتها وإليك فهرس السلسلة:
</p>

<ul>
<li>
		بناء تطبيق جانغو بخادم Gunicorn ووضعه ضمن حاوية دوكر
	</li>
	<li>
		توسيع تطبيق جانغو وتأمينه عبر حاوية دوكر وخادم Nginx وخدمة Let's Encrypt
	</li>
	<li>
		نشر تطبيق جانغو آمن وقابل للتوسيع باستخدام كوبيرنتس
	</li>
</ul>
<h2>
	متطلبات بيئة العمل
</h2>

<p>
	ستحتاج المتطلبات التالية لتتمكن من التطبيق العملي للمقال:
</p>

<ol>
<li>
		ثلاثة خوادم عليها نظام تشغيل أوبنتو (استعملنا في المقال إصدار 18.04 واستعن بمقال <a href="https://academy.hsoub.com/devops/linux/%D8%A7%D9%84%D8%AA%D9%87%D9%8A%D8%A6%D8%A9-%D8%A7%D9%84%D8%A3%D9%88%D9%84%D9%8A%D8%A9-%D9%84%D8%AE%D8%A7%D8%AF%D9%85-%D8%A3%D9%88%D8%A8%D9%88%D9%86%D8%AA%D9%88-1804-r431/" rel="">التهيئة الأولية لخادم أوبونتو 18.04</a>):
	</li>
</ol>
<ul style="margin-right: 40px;">
<li>
		اثنان منها خوادم تطبيق لزوم تشغيل تطبيقك (جانغو/Gunicorn).
	</li>
	<li>
		خادم واحد يؤدي وظيفة الوكيل لزوم تشغيل Nginx و Certbot.
	</li>
	<li>
		مستخدم عادي -غير مسؤول- يتمتع بصلاحية sudo وجدار ناري فعال على الخوادم الثلاثة.
	</li>
</ul>
<ol start="2">
<li>
		تثبيت دوكر على الخوادم الثلاثة، اطلع على <a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D8%AF%D9%88%D9%83%D8%B1-%D9%88%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85%D9%87-%D8%B9%D9%84%D9%89-%D8%AF%D8%A8%D9%8A%D8%A7%D9%86-r465/" rel="">كيفية تثبيت دوكر واستخدامه على دبيان</a> لإنجاز المهمة إذ طريقة التثبيت نفسها في النظامين.
	</li>
	<li>
		حجز اسم نطاق لتطبيقك، سنعتمد في مقالنا على الاسم your_domain.com، مع العلم بإمكانية حصولك على اسم نطاق مجاني من <a href="http://www.freenom.com/en/index.html" rel="external nofollow">Freenom</a>.
	</li>
	<li>
		ربط اسم النطاق بعنوان الـ IP العام للخادم الوكيل عبر إضافة سجل DNS من النوع A، يمكنك الحصول على تفاصيل إضافية حول الموضوع بالاطلاع على <a href="https://academy.hsoub.com/devops/servers/dns/" rel="">القسم الخاص بخدمة اسم النطاق DNS</a> على أكاديمية حسوب.
	</li>
	<li>
		خدمة تخزين كائني متوافقة مع S3 ومع إضافة <a href="https://django-storages.readthedocs.io/en/latest/" rel="external nofollow">django-storages</a> مثل <a href="https://www.digitalocean.com/products/spaces" rel="external nofollow">DigitalOcean Space</a> وظيفتها تأمين المساحة التخزينية اللازمة لملفات تطبيق جانغو الساكنة، مع مجموعة مفاتيح الوصول الخاصة بإدارة هذه المساحة، استرشد <a href="https://docs.digitalocean.com/products/spaces/how-to/create/" rel="external nofollow">بكيفية إعداد مساحة على DigitalOcean Space</a> وعدّل بما يلائم خدمة التخزين التي اخترتها.
	</li>
	<li>
		نظام إدارة قواعد بيانات <a href="https://docs.djangoproject.com/en/2.2/ref/databases/" rel="external nofollow">متوافق مع جانغو</a> مع إنشاء قاعدة بيانات ومستخدم خاص بالتطبيق، نحن اخترنا خادم PostgreSQL، اهتم بالتفاصيل التالية أثناء إنجازك لهذه النقطة لتتمكن من متابعة خطوات المقال:
	</li>
</ol>
<ul style="margin-right: 40px;">
<li>
		استخدم الاسم polls لقاعدة البيانات والاسم sammy لمستخدم قاعدة البيانات (هذه الأسماء ليست ملزمة ولكننا استخدمناها في المقال واستخدامك لها سيسهل عليك المتابعة)، لمزيد من التفاصيل حول الإنشاء استرشد بالخطوة /1/ من مقالنا السابق [بناء تطبيق جانغو بخادم Gunicorn ووضعه ضمن حاوية دوكر]()، مع العلم أن هذه المهمة يمكن إنجازها من أي خادم من الخوادم الثلاثة لبيئة العمل.
	</li>
	<li>
		اعتمدنا في المقال على نظام إدارة قواعد البيانات من نوع <a href="https://www.digitalocean.com/products/managed-databases/" rel="external nofollow">Managed PostgreSQL cluster</a> الذي توفره DigitalOcean.
	</li>
	<li>
		لكن يمكنك تثبيت برنامج PostgreSQL وتشغيله وإعداده بنفسك مع الاستعانة بالفيديو التعليمي <a href="https://academy.hsoub.com/devops/servers/databases/postgresql/%D9%81%D9%8A%D8%AF%D9%8A%D9%88-%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D9%82%D8%A7%D8%B9%D8%AF%D8%A9-%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-postgresql-r400/" rel="">تثبيت وإعداد قاعدة بيانات PostgreSQL</a>.
	</li>
</ul>
<h2>
	الخطوة 1: إعداد خادم التطبيق الأول
</h2>

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

<p>
	تنويه: في حال كنت مستمرًا معنا من المقال السابق [بناء تطبيق جانغو بخادم Gunicorn ووضعه ضمن حاوية دوكر]() فهذه الخطوة ستكون جاهزة لديك ويمكنك الانتقال للخطوة /2/ وهي تجهيز الخادم الثاني.
</p>

<p>
	سنبدأ بتسجيل الدخول إلى خادم التطبيق الأول واستنساخ polls-docker من قسم <a href="https://github.com/do-community/django-polls" rel="external nofollow">تطبيقات جانغو Polls في مستودع GitHub</a> فهذا القسم يتضمن <a href="https://github.com/do-community/django-polls" rel="external nofollow">عينات تجريبية من التطبيق Polls</a> مجهزة خصيصًا لتوثيقات جانغو التعليمية.
</p>

<p>
	أما القسم polls-docker من المستودع فيتضمن نسخًا معدّلة مسبقًا من تطبيق Polls ومجهزة للعمل بفعالية في بيئة الحاويات، راجع مقالنا السابق <a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D8%A8%D9%86%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A8%D8%AE%D8%A7%D8%AF%D9%85-gunicorn-%D9%88%D9%88%D8%B6%D8%B9%D9%87-%D8%B6%D9%85%D9%86-%D8%AD%D8%A7%D9%88%D9%8A%D8%A9-%D8%AF%D9%88%D9%83%D8%B1-r658/" rel="">بناء تطبيق جانغو بخادم Gunicorn ووضعه ضمن حاوية دوكر</a> لمزيد من المعلومات.
</p>

<p>
	إليك تعليمة الاستنساخ اللازم تنفيذها:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_3867_7" style="">
<span class="pln">git clone </span><span class="pun">--</span><span class="pln">single</span><span class="pun">-</span><span class="pln">branch </span><span class="pun">--</span><span class="pln">branch polls</span><span class="pun">-</span><span class="pln">docker https</span><span class="pun">:</span><span class="com">//github.com/do-community/django-polls.git</span></pre>

<p>
	توجه بعدها إلى المجلد django-polls عبر الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3867_9" style="">
<span class="pln">cd django</span><span class="pun">-</span><span class="pln">polls</span></pre>

<p>
	يتضمن هذا المجلد كود جانغو المكتوب بلغة بايثون، ويحتوي أيضًا الملف Dockerfile الذي سيستخدمه دوكر لبناء صورة الحاوية، بالإضافة إلى الملف env الذي يتضمن متغيرات البيئة التي ستمرر للحاوية من بيئة التشغيل المحيطة.
</p>

<p>
	استعرض الملف Dockerfile عبر تعليمة لينكس <code>cat</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3867_11" style="">
<span class="pln">cat </span><span class="typ">Dockerfile</span></pre>

<p>
	وستحصل على الخرج التالي وهو عبارة عن البيانات المكتوبة ضمن الملف Dockerfile:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3867_13" style="">
<span class="pln">FROM python</span><span class="pun">:</span><span class="lit">3.7</span><span class="pun">.</span><span class="lit">4</span><span class="pun">-</span><span class="pln">alpine3</span><span class="pun">.</span><span class="lit">10</span><span class="pln">

ADD django</span><span class="pun">-</span><span class="pln">polls</span><span class="pun">/</span><span class="pln">requirements</span><span class="pun">.</span><span class="pln">txt </span><span class="pun">/</span><span class="pln">app</span><span class="pun">/</span><span class="pln">requirements</span><span class="pun">.</span><span class="pln">txt

RUN set </span><span class="pun">-</span><span class="pln">ex \
    </span><span class="pun">&amp;&amp;</span><span class="pln"> apk add </span><span class="pun">--</span><span class="pln">no</span><span class="pun">-</span><span class="pln">cache </span><span class="pun">--</span><span class="pln">virtual </span><span class="pun">.</span><span class="pln">build</span><span class="pun">-</span><span class="pln">deps postgresql</span><span class="pun">-</span><span class="pln">dev build</span><span class="pun">-</span><span class="pln">base \
    </span><span class="pun">&amp;&amp;</span><span class="pln"> python </span><span class="pun">-</span><span class="pln">m venv </span><span class="pun">/</span><span class="pln">env \
    </span><span class="pun">&amp;&amp;</span><span class="pln"> </span><span class="pun">/</span><span class="pln">env</span><span class="pun">/</span><span class="pln">bin</span><span class="pun">/</span><span class="pln">pip install </span><span class="pun">--</span><span class="pln">upgrade pip \
    </span><span class="pun">&amp;&amp;</span><span class="pln"> </span><span class="pun">/</span><span class="pln">env</span><span class="pun">/</span><span class="pln">bin</span><span class="pun">/</span><span class="pln">pip install </span><span class="pun">--</span><span class="pln">no</span><span class="pun">-</span><span class="pln">cache</span><span class="pun">-</span><span class="pln">dir </span><span class="pun">-</span><span class="pln">r </span><span class="pun">/</span><span class="pln">app</span><span class="pun">/</span><span class="pln">requirements</span><span class="pun">.</span><span class="pln">txt \
    </span><span class="pun">&amp;&amp;</span><span class="pln"> runDeps</span><span class="pun">=</span><span class="str">"$(scanelf --needed --nobanner --recursive /env \
        | awk '{ gsub(/,/, "</span><span class="pln">\nso</span><span class="pun">:</span><span class="str">", $2); print "</span><span class="pln">so</span><span class="pun">:</span><span class="str">" $2 }' \
        | sort -u \
        | xargs -r apk info --installed \
        | sort -u)"</span><span class="pln"> \
    </span><span class="pun">&amp;&amp;</span><span class="pln"> apk add </span><span class="pun">--</span><span class="pln">virtual rundeps $runDeps \
    </span><span class="pun">&amp;&amp;</span><span class="pln"> apk </span><span class="kwd">del</span><span class="pln"> </span><span class="pun">.</span><span class="pln">build</span><span class="pun">-</span><span class="pln">deps

ADD django</span><span class="pun">-</span><span class="pln">polls </span><span class="pun">/</span><span class="pln">app
WORKDIR </span><span class="pun">/</span><span class="pln">app

ENV VIRTUAL_ENV </span><span class="pun">/</span><span class="pln">env
ENV PATH </span><span class="pun">/</span><span class="pln">env</span><span class="pun">/</span><span class="pln">bin</span><span class="pun">:</span><span class="pln">$PATH

EXPOSE </span><span class="lit">8000</span><span class="pln">

CMD </span><span class="pun">[</span><span class="str">"gunicorn"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"--bind"</span><span class="pun">,</span><span class="pln"> </span><span class="str">":8000"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"--workers"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"3"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"mysite.wsgi"</span><span class="pun">]</span></pre>

<p>
	انظر تعليمات الملف السابق بالترتيب وتابع الشرح الآتي.
</p>

<p>
	سيستخدم الملف <a href="https://hub.docker.com/_/python" rel="external nofollow">صورة دوكر</a> بإصدار بايثون 3.7.4، ويثبت اعتماديات وحزم بايثون اللازمة لكل من جانغو وخادم gunicorn وفق ملف متطلبات التطبيق django-polls/requirements.txt ومن ثم يحذف الملفات غير اللازمة بعد انتهاء التثبيت، وينسخ بعد ذلك كود التطبيق إلى الصورة ويسند قيمة المتغير <code>PATH</code>، وأخيرًا سيحدد البوابة 8000 لاستقبال حركة البيانات الواردة إلى الحاوية، ويشغل خادم gunicorn مع عمال workers عدد /3/ عبر البوابة 8000.
</p>

<p>
	لننتقل إلى بناء صورة الحاوية باستخدام <code>docker build</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3867_15" style="">
<span class="pln">docker build </span><span class="pun">-</span><span class="pln">t polls </span><span class="pun">.</span></pre>

<p>
	سميّنا الصورة <code>polls</code> باستخدام الراية <code>t-</code> ومررنا المسار الحالي <code>.</code> كمسار context للتعليمة build فهو يتضمن كافة الملفات اللازمة لعملية البناء.
</p>

<p>
	بعد انتهاء عملية البناء استخدم الأمر <code>docker images</code> لاستعراض الصور المتاحة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3867_17" style="">
<span class="pln">docker images</span></pre>

<p>
	وستحصل على الخرج التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3867_20" style="">
<span class="pln">REPOSITORY          TAG                    IMAGE ID             CREATED              SIZE
polls                           latest                  </span><span class="lit">80ec4f33aae1</span><span class="pln">         </span><span class="lit">2</span><span class="pln"> weeks ago          </span><span class="lit">197MB</span><span class="pln">
python                   </span><span class="lit">3.7</span><span class="pun">.</span><span class="lit">4</span><span class="pun">-</span><span class="pln">alpine3</span><span class="pun">.</span><span class="lit">10</span><span class="pln">     f309434dea3a       </span><span class="lit">8</span><span class="pln"> months ago        </span><span class="lit">98.7MB</span></pre>

<p>
	تشغيل الصورة هي الخطوة التالية لعملية البناء، إلا أنها تتطلب ضبط متغيرات البيئة الموجودة ضمن الملف env وهو أحد لوازم تعليمة التشغيل <code>docker run</code>، لنعدّل عليه: افتح الملف env الموجود ضمن المجلد <code>django-polls</code> باستخدام محرر النصوص nano:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3867_22" style="">
<span class="pln">nano env</span></pre>

<p>
	سيظهر أمامك الملف بالشكل التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3867_24" style="">
<span class="pln">DJANGO_SECRET_KEY</span><span class="pun">=</span><span class="pln">
DEBUG</span><span class="pun">=</span><span class="kwd">True</span><span class="pln">
DJANGO_ALLOWED_HOSTS</span><span class="pun">=</span><span class="pln">
DATABASE_ENGINE</span><span class="pun">=</span><span class="pln">postgresql_psycopg2
DATABASE_NAME</span><span class="pun">=</span><span class="pln">polls
DATABASE_USERNAME</span><span class="pun">=</span><span class="pln">
DATABASE_PASSWORD</span><span class="pun">=</span><span class="pln">
DATABASE_HOST</span><span class="pun">=</span><span class="pln">
DATABASE_PORT</span><span class="pun">=</span><span class="pln">
STATIC_ACCESS_KEY_ID</span><span class="pun">=</span><span class="pln">
STATIC_SECRET_KEY</span><span class="pun">=</span><span class="pln">
STATIC_BUCKET_NAME</span><span class="pun">=</span><span class="pln">
STATIC_ENDPOINT_URL</span><span class="pun">=</span><span class="pln">
DJANGO_LOGLEVEL</span><span class="pun">=</span><span class="pln">info</span></pre>

<p>
	كما ترى فبعض محددات الملف قيمها موجودة والبعض الآخر يحتاج إلى ضبط بما يناسب بيئتك، سنذكر المحددات التي تحتاج إلى ضبط بالترتيب:
</p>

<ol>
<li>
		المفتاح السري <code>DJANGO_SECRET_KEY</code>: اختر له قيمة فريدة صعبة التخمين كما توصي <a href="https://docs.djangoproject.com/en/2.2/ref/settings/#secret-key" rel="external nofollow">توثيقات Docker</a> نقدم لك في الخطوة /5/ من مقال <a href="https://www.digitalocean.com/community/tutorials/how-to-set-up-a-scalable-django-app-with-digitalocean-managed-databases-and-spaces#step-5-adjusting-the-app-settings" rel="external nofollow">كيفية إعداد تطبيق جانغو عبر DigitalOcean Managed Databases and Spaces</a> إحدى طرق جانغو لتوليد المفاتيح.
	</li>
	<li>
		<code>DJANGO_ALLOWED_HOSTS</code>: أسند له اسم النطاق الخاص بك <code>your_domain.com</code>، أو يمكنك وضع الرمز <code>*</code> لأغراض الاختبار فقط وليس ضمن بيئة العمل الفعلية فهذا الرمز يسمح لأي عنوان IP بالوصول، يحميك هذا المحدد من الهجمات المعتمدة على حقن ترويسة HTTP وفي حال أردت المزيد من المعلومات اطلع على توثيقات جانغو الخاصة بهذا المحدد ضمن <a href="https://docs.djangoproject.com/en/3.0/ref/settings/#allowed-hosts" rel="external nofollow">Core Settings</a>
	</li>
	<li>
		<code>DATABASE_USERNAME</code>: ضع اسم مستخدم قاعدة البيانات PostgreSQL الذي أنشأته أثناء إعدادك لمتطلبات العمل.
	</li>
	<li>
		<code>DATABASE_NAME</code>: ضع القيمة <code>polls</code> أو الاسم الذي اخترته لقاعدة البيانات أثناء إعدادك لمتطلبات العمل.
	</li>
	<li>
		كذلك الأمر لكلمة المرور <code>DATABASE_PASSWORD</code>.
	</li>
	<li>
		ضع اسم مضيف قاعدة البيانات <code>DATABASE_HOST</code> ورقم بوابة الاتصال معها <code>DATABASE_PORT</code>
	</li>
	<li>
		أما المحددات <code>STATIC_ACCESS_KEY_ID</code> و <code>STATIC_SECRET_KEY</code> و <code>STATIC_BUCKET_NAME</code> و <code>STATIC_ENDPOINT_URL</code> فهي تتعلق بخدمة التخزين الكائني الخارجية، اضبطها بما يتناسب مع الخدمة التي تستخدمها.
	</li>
</ol>
<p>
	احفظ التغيرات على الملف env بعد الانتهاء وأغلقه استعدادًا لتنفيذ أمر تشغيل الحاوية عبر التعليمة <code>docker run</code> التي سنكتبها بطريقة تسمح بتجاوز <code>CMD</code> (التي تحدد افتراضيات بدء التشغيل) وتنشئ أثناء التشغيل مخطط قاعدة البيانات باستخدام كل من <code>manage.py makemigrations</code> و <code>manage.py migrate</code> وفق ما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3867_26" style="">
<span class="pln">docker run </span><span class="pun">--</span><span class="pln">env</span><span class="pun">-</span><span class="pln">file env polls sh </span><span class="pun">-</span><span class="pln">c </span><span class="str">"python manage.py makemigrations &amp;&amp; python manage.py migrate"</span></pre>

<p>
	لنشرح الأمر السابق، شغلنا صورة الحاوية <code>polls:latest</code> ومررنا لها متغيرات البيئة التي ضبطناها للتو ضمن الملف <code>env</code>، وتجاوزنا الأمر الافتراضي في ملف <code>Dockerfile</code> لننفذ الأمر التالي بدلًا منه الذي أنشأ مخطط قاعدة البيانات بالمواصفات المحددة في كود التطبيق:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3867_28" style="">
<span class="pln">sh </span><span class="pun">-</span><span class="pln">c </span><span class="str">"python manage.py makemigrations &amp;&amp; python manage.py migrate"</span></pre>

<p>
	إن كنت تنفذ <code>docker run</code> للمرة الأولى فستحصل على الخرج التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3867_30" style="">
<span class="typ">No</span><span class="pln"> changes detected
</span><span class="typ">Operations</span><span class="pln"> to perform</span><span class="pun">:</span><span class="pln">
  </span><span class="typ">Apply</span><span class="pln"> all migrations</span><span class="pun">:</span><span class="pln"> admin</span><span class="pun">,</span><span class="pln"> auth</span><span class="pun">,</span><span class="pln"> contenttypes</span><span class="pun">,</span><span class="pln"> polls</span><span class="pun">,</span><span class="pln"> sessions
</span><span class="typ">Running</span><span class="pln"> migrations</span><span class="pun">:</span><span class="pln">
  </span><span class="typ">Applying</span><span class="pln"> contenttypes</span><span class="pun">.</span><span class="lit">0001</span><span class="pln">_initial</span><span class="pun">...</span><span class="pln"> OK
  </span><span class="typ">Applying</span><span class="pln"> auth</span><span class="pun">.</span><span class="lit">0001</span><span class="pln">_initial</span><span class="pun">...</span><span class="pln"> OK
  </span><span class="typ">Applying</span><span class="pln"> admin</span><span class="pun">.</span><span class="lit">0001</span><span class="pln">_initial</span><span class="pun">...</span><span class="pln"> OK
  </span><span class="typ">Applying</span><span class="pln"> admin</span><span class="pun">.</span><span class="lit">0002</span><span class="pln">_logentry_remove_auto_add</span><span class="pun">...</span><span class="pln"> OK
  </span><span class="typ">Applying</span><span class="pln"> admin</span><span class="pun">.</span><span class="lit">0003</span><span class="pln">_logentry_add_action_flag_choices</span><span class="pun">...</span><span class="pln"> OK
  </span><span class="typ">Applying</span><span class="pln"> contenttypes</span><span class="pun">.</span><span class="lit">0002</span><span class="pln">_remove_content_type_name</span><span class="pun">...</span><span class="pln"> OK
  </span><span class="typ">Applying</span><span class="pln"> auth</span><span class="pun">.</span><span class="lit">0002</span><span class="pln">_alter_permission_name_max_length</span><span class="pun">...</span><span class="pln"> OK
  </span><span class="typ">Applying</span><span class="pln"> auth</span><span class="pun">.</span><span class="lit">0003</span><span class="pln">_alter_user_email_max_length</span><span class="pun">...</span><span class="pln"> OK
  </span><span class="typ">Applying</span><span class="pln"> auth</span><span class="pun">.</span><span class="lit">0004</span><span class="pln">_alter_user_username_opts</span><span class="pun">...</span><span class="pln"> OK
  </span><span class="typ">Applying</span><span class="pln"> auth</span><span class="pun">.</span><span class="lit">0005</span><span class="pln">_alter_user_last_login_null</span><span class="pun">...</span><span class="pln"> OK
  </span><span class="typ">Applying</span><span class="pln"> auth</span><span class="pun">.</span><span class="lit">0006</span><span class="pln">_require_contenttypes_0002</span><span class="pun">...</span><span class="pln"> OK
  </span><span class="typ">Applying</span><span class="pln"> auth</span><span class="pun">.</span><span class="lit">0007</span><span class="pln">_alter_validators_add_error_messages</span><span class="pun">...</span><span class="pln"> OK
  </span><span class="typ">Applying</span><span class="pln"> auth</span><span class="pun">.</span><span class="lit">0008</span><span class="pln">_alter_user_username_max_length</span><span class="pun">...</span><span class="pln"> OK
  </span><span class="typ">Applying</span><span class="pln"> auth</span><span class="pun">.</span><span class="lit">0009</span><span class="pln">_alter_user_last_name_max_length</span><span class="pun">...</span><span class="pln"> OK
  </span><span class="typ">Applying</span><span class="pln"> auth</span><span class="pun">.</span><span class="lit">0010</span><span class="pln">_alter_group_name_max_length</span><span class="pun">...</span><span class="pln"> OK
  </span><span class="typ">Applying</span><span class="pln"> auth</span><span class="pun">.</span><span class="lit">0011</span><span class="pln">_update_proxy_permissions</span><span class="pun">...</span><span class="pln"> OK
  </span><span class="typ">Applying</span><span class="pln"> polls</span><span class="pun">.</span><span class="lit">0001</span><span class="pln">_initial</span><span class="pun">...</span><span class="pln"> OK
  </span><span class="typ">Applying</span><span class="pln"> sessions</span><span class="pun">.</span><span class="lit">0001</span><span class="pln">_initial</span><span class="pun">...</span><span class="pln"> OK</span></pre>

<p>
	مع العلم أن جانغو لن يجري أي عملية عندما تنفذ <code>migrate</code> في المرات القادمة طالما لم يطرأ أي تغيير على مخطط قاعدة بيانات التطبيق.
</p>

<p>
	نفذ بعدها أمر تشغيل جديد للحاوية مع استدعاء سطر الأوامر الخاص بالصدفة Shell كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3867_32" style="">
<span class="pln">docker run </span><span class="pun">-</span><span class="pln">i </span><span class="pun">-</span><span class="pln">t </span><span class="pun">--</span><span class="pln">env</span><span class="pun">-</span><span class="pln">file env polls sh</span></pre>

<p>
	تظهر بنتيجة هذا الأمر نافذة الصدفة shell ضمن الحاوية اكتب فيها تعليمة إنشاء المستخدم الخاص بتطبيق جانغو:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3867_34" style="">
<span class="pln">python manage</span><span class="pun">.</span><span class="pln">py createsuperuser</span></pre>

<p>
	استكمل بياناته وهي اسم المستخدم وكلمة المرور والبريد الإلكتروني، واضغط بعدها على الاختصار Ctrl+D للخروج من الحاوية وإنهاء عملها.
</p>

<p>
	أما العملية الأخيرة فهي جمع الملفات الساكنة الخاصة بالتطبيق <code>collectstatic</code> ورفعها على خدمة التخزين الكائني وتتم وفق الأمر التالي علمًا أنها قد تستغرق بعض الوقت:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3867_36" style="">
<span class="pln">docker run </span><span class="pun">--</span><span class="pln">env</span><span class="pun">-</span><span class="pln">file env django</span><span class="pun">-</span><span class="pln">polls</span><span class="pun">:</span><span class="pln">v0 sh </span><span class="pun">-</span><span class="pln">c </span><span class="str">"python manage.py collectstatic --noinput"</span></pre>

<p>
	يظهر بعدها الخرج التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3867_38" style="">
<span class="lit">121</span><span class="pln"> static files copied</span><span class="pun">.</span></pre>

<p>
	يمكنك الآن تشغيل التطبيق:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3867_41" style="">
<span class="pln">docker run </span><span class="pun">--</span><span class="pln">env</span><span class="pun">-</span><span class="pln">file env </span><span class="pun">-</span><span class="pln">p </span><span class="lit">80</span><span class="pun">:</span><span class="lit">8000</span><span class="pln"> polls</span></pre>

<p>
	ستحصل على الخرج:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3867_43" style="">
<span class="pun">[</span><span class="lit">2019</span><span class="pun">-</span><span class="lit">10</span><span class="pun">-</span><span class="lit">17</span><span class="pln"> </span><span class="lit">21</span><span class="pun">:</span><span class="lit">23</span><span class="pun">:</span><span class="lit">36</span><span class="pln"> </span><span class="pun">+</span><span class="lit">0000</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="lit">1</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="pln">INFO</span><span class="pun">]</span><span class="pln"> </span><span class="typ">Starting</span><span class="pln"> gunicorn </span><span class="lit">19.9</span><span class="pun">.</span><span class="lit">0</span><span class="pln">
</span><span class="pun">[</span><span class="lit">2019</span><span class="pun">-</span><span class="lit">10</span><span class="pun">-</span><span class="lit">17</span><span class="pln"> </span><span class="lit">21</span><span class="pun">:</span><span class="lit">23</span><span class="pun">:</span><span class="lit">36</span><span class="pln"> </span><span class="pun">+</span><span class="lit">0000</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="lit">1</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="pln">INFO</span><span class="pun">]</span><span class="pln"> </span><span class="typ">Listening</span><span class="pln"> at</span><span class="pun">:</span><span class="pln"> http</span><span class="pun">://</span><span class="lit">0.0</span><span class="pun">.</span><span class="lit">0.0</span><span class="pun">:</span><span class="lit">8000</span><span class="pln"> </span><span class="pun">(</span><span class="lit">1</span><span class="pun">)</span><span class="pln">
</span><span class="pun">[</span><span class="lit">2019</span><span class="pun">-</span><span class="lit">10</span><span class="pun">-</span><span class="lit">17</span><span class="pln"> </span><span class="lit">21</span><span class="pun">:</span><span class="lit">23</span><span class="pun">:</span><span class="lit">36</span><span class="pln"> </span><span class="pun">+</span><span class="lit">0000</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="lit">1</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="pln">INFO</span><span class="pun">]</span><span class="pln"> </span><span class="typ">Using</span><span class="pln"> worker</span><span class="pun">:</span><span class="pln"> sync
</span><span class="pun">[</span><span class="lit">2019</span><span class="pun">-</span><span class="lit">10</span><span class="pun">-</span><span class="lit">17</span><span class="pln"> </span><span class="lit">21</span><span class="pun">:</span><span class="lit">23</span><span class="pun">:</span><span class="lit">36</span><span class="pln"> </span><span class="pun">+</span><span class="lit">0000</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="lit">7</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="pln">INFO</span><span class="pun">]</span><span class="pln"> </span><span class="typ">Booting</span><span class="pln"> worker </span><span class="kwd">with</span><span class="pln"> pid</span><span class="pun">:</span><span class="pln"> </span><span class="lit">7</span><span class="pln">
</span><span class="pun">[</span><span class="lit">2019</span><span class="pun">-</span><span class="lit">10</span><span class="pun">-</span><span class="lit">17</span><span class="pln"> </span><span class="lit">21</span><span class="pun">:</span><span class="lit">23</span><span class="pun">:</span><span class="lit">36</span><span class="pln"> </span><span class="pun">+</span><span class="lit">0000</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="lit">8</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="pln">INFO</span><span class="pun">]</span><span class="pln"> </span><span class="typ">Booting</span><span class="pln"> worker </span><span class="kwd">with</span><span class="pln"> pid</span><span class="pun">:</span><span class="pln"> </span><span class="lit">8</span><span class="pln">
</span><span class="pun">[</span><span class="lit">2019</span><span class="pun">-</span><span class="lit">10</span><span class="pun">-</span><span class="lit">17</span><span class="pln"> </span><span class="lit">21</span><span class="pun">:</span><span class="lit">23</span><span class="pun">:</span><span class="lit">36</span><span class="pln"> </span><span class="pun">+</span><span class="lit">0000</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="lit">9</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="pln">INFO</span><span class="pun">]</span><span class="pln"> </span><span class="typ">Booting</span><span class="pln"> worker </span><span class="kwd">with</span><span class="pln"> pid</span><span class="pun">:</span><span class="pln"> </span><span class="lit">9</span></pre>

<p>
	مع العلم أن التشغيل هذه المرة وفق الإعدادات الافتراضية المنصوص عليها في ملف Dockerfile وهي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3867_45" style="">
<span class="pln">gunicorn </span><span class="pun">--</span><span class="pln">bind </span><span class="pun">:</span><span class="lit">8000</span><span class="pln"> </span><span class="pun">--</span><span class="pln">workers </span><span class="lit">3</span><span class="pln"> mysite</span><span class="pun">.</span><span class="pln">wsgi</span><span class="pun">:</span><span class="pln">application</span></pre>

<p>
	وأن البوابة أو المنفذ 8000 للحاوية فُتحت لتستقبل حركة البيانات القادمة من المنفذ 80 للخادم أوبونتو.
</p>

<p>
	يمكنك الآن كتابة عنوان URL الخاص بالتطبيق polls بمتصفح الإنترنت واستعراضه، لاحظ أنك ستحصل على الخطأ 404 (لم يتم العثور على الصفحة) في حال اكتفيت بالعنوان:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_3867_47" style="">
<span class="pln">http</span><span class="pun">:</span><span class="com">//APP_SERVER_1_IP/</span></pre>

<p>
	بسبب عدم وجود بيانات للتطبيق تحت الجذر <code>/</code> لذا اكتب العنوان التالي لتحصل على واجهة polls:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_3867_49" style="">
<span class="pln">http</span><span class="pun">:</span><span class="com">//APP_SERVER_1_IP/polls</span></pre>

<p>
	** تنويه **: إذا كنت تستخدم جدارًا ناريًا من النوع UFW فإنك ستلاحظ وجود ثغرة أمنية في عمل حاوية دوكر وهي أن دوكر يتجاهل كافة السياسات الأمنية الموجودة على هذا النوع من الجدران النارية ويسمح بالاتصالات بغض النظر عنها، فقد مكّن الاتصال مع البوابة 80 على الخادم دون أن نضبط مسبقًا أي إعداد على الجدار الناري، هذه الثغرة في الواقع موثقة على <a href="https://github.com/docker/for-linux/issues/690" rel="external nofollow">GitHub Issue</a> وتعالجها الخطوة /5/ من مقالنا عبر تصحيح إعدادات <a href="https://academy.hsoub.com/devops/security/firewalls/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-ufw-%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D9%88%D8%A3%D9%88%D8%A7%D9%85%D8%B1-%D8%B4%D8%A7%D8%A6%D8%B9%D8%A9-%D9%84%D9%84%D8%AC%D8%AF%D8%A7%D8%B1-%D8%A7%D9%84%D9%86%D8%A7%D8%B1%D9%8A-r121/" rel="">الجدار الناري UFW</a>، أما في حال كنت تستخدم جدارًا ناريًا أقوى وأكثر تطورًا كالذي توفره <a href="https://docs.digitalocean.com/products/networking/firewalls/" rel="external nofollow">DigitalOcean’s Cloud Firewalls</a> على سبيل المثال يمكنك تخطي هذا التحذير.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="111577" href="https://academy.hsoub.com/uploads/monthly_2022_11/01-img-polls_app.png.2127ceed42d9dbf77b01cc2ff5474e8b.png" rel=""><img alt="01-img-polls_app.png" class="ipsImage ipsImage_thumbnailed" data-fileid="111577" data-unique="l3ejufp95" src="https://academy.hsoub.com/uploads/monthly_2022_11/01-img-polls_app.png.2127ceed42d9dbf77b01cc2ff5474e8b.png"></a>
</p>

<p>
	اكتب الرابط التالي http://APP_SERVER_1_ip/admin للوصول إلى واجهة الدخول للوحة التحكم الخاصة بالتطبيق:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="111578" href="https://academy.hsoub.com/uploads/monthly_2022_11/02-img-polls_admin.png.2a356e560cd80c65ff6d72163c56545e.png" rel=""><img alt="واجهة الدخول للوحة التحكم الخاصة بالتطبيق" class="ipsImage ipsImage_thumbnailed" data-fileid="111578" data-unique="3137d1bb8" src="https://academy.hsoub.com/uploads/monthly_2022_11/02-img-polls_admin.png.2a356e560cd80c65ff6d72163c56545e.png" style="width: 500px; height: auto;"></a>
</p>

<p>
	أدخل بيانات المستخدم مدير التطبيق المنشأ سابقًا باستخدام الأمر <code>createsuperuser</code> وستظهر أمامك واجهة الإدارة والتحكم الخاصة بالتطبيق.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="111579" href="https://academy.hsoub.com/uploads/monthly_2022_11/03-img-polls_admin_main.png.e239e0ab385827a4ac328756862fde28.png" rel=""><img alt="واجهة الإدارة والتحكم الخاصة بالتطبيق" class="ipsImage ipsImage_thumbnailed" data-fileid="111579" data-unique="hvmmenvxt" src="https://academy.hsoub.com/uploads/monthly_2022_11/03-img-polls_admin_main.thumb.png.743c4a280f287c07f5d00cc4f174e1a4.png" style="width: 700px; height: auto;"></a>
</p>

<p>
	تذكر أن تسليم المكونات الساكنة للتطبيق admin و polls يتم من خدمة التخزين الكائني ويمكنك اتباع هذه الخطوات لاختبار جودته وأيضًا <a href="https://www.digitalocean.com/community/tutorials/how-to-set-up-a-scalable-django-app-with-digitalocean-managed-databases-and-spaces#testing-spaces-static-file-delivery" rel="external nofollow">التأكد من صحة إحضار الملفات</a>.
</p>

<p>
	بعد أن تنتهي من التصفح اضغط <code>Ctrl+c</code> في نافذة كتابة الأوامر السطرية التي تشغل حاوية دوكر لتنهي عمل الحاوية ثم شغلها مجددًا بالنمط المنفصل detached الذي يسمح لها أن تعمل بالخلفية ويمكنك من تسجيل الخروج من جلسة <abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة">ssh</abbr>.
</p>

<p>
	اكتب الأمر التالي لتشغيل النمط المنفصل:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3867_54" style="">
<span class="pln">docker run </span><span class="pun">-</span><span class="pln">d </span><span class="pun">--</span><span class="pln">rm </span><span class="pun">--</span><span class="pln">name polls </span><span class="pun">--</span><span class="pln">env</span><span class="pun">-</span><span class="pln">file env </span><span class="pun">-</span><span class="pln">p </span><span class="lit">80</span><span class="pun">:</span><span class="lit">8000</span><span class="pln"> polls</span></pre>

<p>
	تشير الراية <code>d-</code> إلى النمط المنفصل، بينما تشير الراية <code>rm--</code> إلى تنظيف نظام ملفات الحاوية بعد إغلاقها، أما <code>polls</code> فهو اسم الحاوية.
</p>

<p>
	ننهي بذلك إعداد خادم التطبيق الأول سجل الخروج منه، وافتح رابط التطبيق من المتصفح للمرة الأخيرة للتأكد من عمله بشكل سليم قبل انتقالنا للخطوة /2/ الخاصة بتجهيز الخادم الثاني.
</p>

<h2>
	الخطوة 2: إعداد خادم التطبيق الثاني
</h2>

<p>
	سنعرض الأوامر باختصار في هذه الخطوة نظرًا للتشابه الكبير بين إعداد هذا الخادم وخادم التطبيق الأول الذي شرحناه في الخطوة /1/، لنباشر التطبيق العملي.
</p>

<p>
	سجل الدخول إلى خادم التطبيقات الثاني وابدأ باستنساخ القسم polls-docker من مستودع django-polls على GitHub:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_3867_56" style="">
<span class="pln">git clone </span><span class="pun">--</span><span class="pln">single</span><span class="pun">-</span><span class="pln">branch </span><span class="pun">--</span><span class="pln">branch polls</span><span class="pun">-</span><span class="pln">docker https</span><span class="pun">:</span><span class="com">//github.com/do-community/django-polls.git</span></pre>

<p>
	توجه إلى المسار التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_3867_58" style="">
<span class="pln">cd django</span><span class="pun">-</span><span class="pln">polls</span></pre>

<p>
	أنشئ صورة التطبيق باستخدام <code>docker build</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3867_62" style="">
<span class="pln">docker build </span><span class="pun">-</span><span class="pln">t polls </span><span class="pun">.</span></pre>

<p>
	افتح الملف env بمحرر النصوص الذي تفضله:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3867_64" style="">
<span class="pln">nano env</span></pre>

<p>
	لتحصل على الخرج التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3867_70" style="">
<span class="pln">DJANGO_SECRET_KEY</span><span class="pun">=</span><span class="pln">
DEBUG</span><span class="pun">=</span><span class="kwd">True</span><span class="pln">
DJANGO_ALLOWED_HOSTS</span><span class="pun">=</span><span class="pln">
DATABASE_ENGINE</span><span class="pun">=</span><span class="pln">postgresql_psycopg2
DATABASE_NAME</span><span class="pun">=</span><span class="pln">polls
DATABASE_USERNAME</span><span class="pun">=</span><span class="pln">
DATABASE_PASSWORD</span><span class="pun">=</span><span class="pln">
DATABASE_HOST</span><span class="pun">=</span><span class="pln">
DATABASE_PORT</span><span class="pun">=</span><span class="pln">
STATIC_ACCESS_KEY_ID</span><span class="pun">=</span><span class="pln">
STATIC_SECRET_KEY</span><span class="pun">=</span><span class="pln">
STATIC_BUCKET_NAME</span><span class="pun">=</span><span class="pln">
STATIC_ENDPOINT_URL</span><span class="pun">=</span><span class="pln">
DJANGO_LOGLEVEL</span><span class="pun">=</span><span class="pln">info</span></pre>

<p>
	اضبط قيم الملف بما يناسب بيئتك ثم احفظ التغييرات على الملف وأغلقه.
</p>

<p>
	وأخيرًا شغل حاوية التطبيق بالنمط المنفصل كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3867_68" style="">
<span class="pln">docker run </span><span class="pun">-</span><span class="pln">d </span><span class="pun">--</span><span class="pln">rm </span><span class="pun">--</span><span class="pln">name polls </span><span class="pun">--</span><span class="pln">env</span><span class="pun">-</span><span class="pln">file env </span><span class="pun">-</span><span class="pln">p </span><span class="lit">80</span><span class="pun">:</span><span class="lit">8000</span><span class="pln"> polls</span></pre>

<p>
	وافتح التطبيق باستخدام المتصفح للتأكد من سلامة عمله:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_3867_75" style="">
<span class="pln">http</span><span class="pun">:</span><span class="com">//APP_SERVER_2_IP/polls </span></pre>

<p>
	لاحظ الفرق الذي أحدثه النمط المنفصل فيمكنك الآن تسجيل الخروج من الخادم الثاني دون إنهاء عمل الحاوية فهي تعمل في الخلفية.
</p>

<p>
	أنهينا تجهيز خادمي تطبيق جانغو لننتقل للخطوة التالية ونجهز حاوية الوكيل العكسي.
</p>

<h2>
	الخطوة 3: إعداد حاوية دوكر للخادم Nginx
</h2>

<p>
	يتمتع خادم الويب Nginx بالعديد من المزايا أهمها قدرته على أداء وظائف متعددة مثل <a href="https://academy.hsoub.com/devops/servers/web/nginx/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-nginx-%D9%83%D9%88%D8%B3%D9%8A%D8%B7-%D8%B9%D9%83%D8%B3%D9%8A-reverse-proxy-%D9%84%D9%80apache-r19/" rel="">الوكيل العكسي</a> و<a href="https://academy.hsoub.com/devops/servers/%D9%85%D8%A7-%D8%A7%D9%84%D9%85%D9%82%D8%B5%D9%88%D8%AF-%D8%A8%D8%AA%D9%88%D8%B2%D9%8A%D8%B9-%D8%A7%D9%84%D8%AD%D9%90%D9%85%D9%84-load-balancing-r319/" rel="">موازنة الحمل</a> و<a href="https://en.wikipedia.org/wiki/Web_cache" rel="external nofollow">التخزين لملفات الموقع</a>، سيؤدي منها في هذه البيئة وظيفة الوكيل العكسي لحماية الواجهات الخلفية لخادمي جانغو وموازن الحمل لتوزيع الطلبات بينهما، كما سيؤمن تشفير الاتصالات مع خوادم التطبيق باستخدام شهادة <a href="https://academy.hsoub.com/devops/servers/databases/mysql/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%B6%D8%A8%D8%B7-%D8%AA%D8%B4%D9%81%D9%8A%D8%B1-ssltls-%D9%84%D9%84%D8%A7%D8%AA%D8%B5%D8%A7%D9%84%D8%A7%D8%AA-%D8%A5%D9%84%D9%89-%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-mysql-%D8%B9%D9%84%D9%89-%D8%AE%D8%A7%D8%AF%D9%88%D9%85-%D8%A3%D9%88%D8%A8%D9%86%D8%AA%D9%88-1604-r341/" rel=""><abbr title="Transport Layer Security | بروتوكول أمن طبقة النقل">TLS</abbr></a> التي يوفرها Certbot ما يعني أنه سيجبر العميل على استخدام HTTPS عبر إعادة توجيه كافة الطلبات من نوع HTTP إلى المنفذ 443، ومن ثم إيصال طلبات HTTPS إلى خوادم جانغو بعد فك تشفيرها، أما وظيفة التخزين فلن نحتاجها إذ الآلية التي استخدمناها لإفراغ ملفات جانغو على وحدة التخزين الكائني تفي بالغرض.
</p>

<p>
	ونوّد أن نبين لك أن التصميم الذي اخترناه في هذا المقال والمؤلف من خادمي تطبيق كل منهما في حاوية مع حاوية ثالثة تتضمن Nginx هو واحد من عدة خيارات متاحة لكل منها مزايا مختلفة من حيث الأداء والحماية، فعلى سبيل المثال كان الممكن بناء Nginx ضمن إحدى حاويتي التطبيق ليقوم بدور الوكيل محليًا لخادم التطبيق الموجود معه في الحاوية نفسها وللخادم في الحاوية الثانية، وفي احتمالٍ آخر يمكنك بناء حاويتي Nginx فتصبح البنية حاوية Nginx مقابل كل حاوية تطبيق ومن ثم استخدام موازن حمل سحابي من أي مزود خدمة مثل <a href="https://www.digitalocean.com/products/load-balancer/" rel="external nofollow">DigitalOcean</a>، أما المفاضلة بين هذه التصاميم المختلفة أنجزها بناءً على تحليل متطلباتك ونتائج <a href="https://en.wikipedia.org/wiki/TLS_termination_proxy" rel="external nofollow">اختبار الحمل Load Test</a> الذي يبين لك نقاط الاختناق في كل بنية لتوسعها، فبنية مقالنا مثلًا تتمتع بالمرونة لجهة قابليتها للتوسع بإضافة حاوية Nginx ثانية أو أكثر في أي مرحلة تظهر فيها اختناقات الشبكة بسبب خادم Nginx الوحيد، كذلك يمكننا الاعتماد على موزان حمل سحابي بسرعة أعلى أو موازن حمل من رتبة الطبقة الرابعة L4 مثل <a href="https://academy.hsoub.com/devops/servers/%D9%85%D9%82%D8%AF%D9%91%D9%85%D8%A9-%D8%A5%D9%84%D9%89-haproxy-%D9%88%D9%85%D8%A8%D8%A7%D8%AF%D8%A6-%D9%85%D9%88%D8%A7%D8%B2%D9%86%D8%A9-%D8%A7%D9%84%D8%AD%D9%85%D9%84-load-balancing-r41/" rel="">HAProxy</a>. والآن بعد أن أعطينا فكرة عن الاحتمالات المتعددة لتصميم البنية لنرجع إلى تطبيقنا العملي.
</p>

<p>
	سجل الدخول إلى الوكيل العكسي Nginx وأنشئ مسارًا يدعى config كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_3867_77" style="">
<span class="pln">mkdir conf</span></pre>

<p>
	ومن ثم أنشئ ملفًا نصيًا باسم nginx.conf باستعمال أحد محررات النصوص مثل nano:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3867_79" style="">
<span class="pln">nano conf</span><span class="pun">/</span><span class="pln">nginx</span><span class="pun">.</span><span class="pln">conf</span></pre>

<p>
	والصق ضمن هذا الملف الإعدادات التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3867_84" style="">
<span class="pln">upstream django </span><span class="pun">{</span><span class="pln">
    server APP_SERVER_1_IP</span><span class="pun">;</span><span class="pln">
    server APP_SERVER_2_IP</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

server </span><span class="pun">{</span><span class="pln">
    listen </span><span class="lit">80</span><span class="pln"> default_server</span><span class="pun">;</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="lit">444</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

server </span><span class="pun">{</span><span class="pln">
    listen </span><span class="lit">80</span><span class="pun">;</span><span class="pln">
    listen </span><span class="pun">[::]:</span><span class="lit">80</span><span class="pun">;</span><span class="pln">
    server_name your_domain</span><span class="pun">.</span><span class="pln">com</span><span class="pun">;</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="lit">301</span><span class="pln"> https</span><span class="pun">://</span><span class="pln">$server_name$request_uri</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

server </span><span class="pun">{</span><span class="pln">
    listen </span><span class="lit">443</span><span class="pln"> <abbr title="Secure Socket Layer | طبقة المنافذ الآمنة">ssl</abbr> http2</span><span class="pun">;</span><span class="pln">
    listen </span><span class="pun">[::]:</span><span class="lit">443</span><span class="pln"> <abbr title="Secure Socket Layer | طبقة المنافذ الآمنة">ssl</abbr> http2</span><span class="pun">;</span><span class="pln">
    server_name your_domain</span><span class="pun">.</span><span class="pln">com</span><span class="pun">;</span><span class="pln">

    </span><span class="com"># <abbr title="Secure Socket Layer | طبقة المنافذ الآمنة">SSL</abbr></span><span class="pln">
    ssl_certificate </span><span class="pun">/</span><span class="pln">etc</span><span class="pun">/</span><span class="pln">letsencrypt</span><span class="pun">/</span><span class="pln">live</span><span class="pun">/</span><span class="pln">your_domain</span><span class="pun">.</span><span class="pln">com</span><span class="pun">/</span><span class="pln">fullchain</span><span class="pun">.</span><span class="pln">pem</span><span class="pun">;</span><span class="pln">
    ssl_certificate_key </span><span class="pun">/</span><span class="pln">etc</span><span class="pun">/</span><span class="pln">letsencrypt</span><span class="pun">/</span><span class="pln">live</span><span class="pun">/</span><span class="pln">your_domain</span><span class="pun">.</span><span class="pln">com</span><span class="pun">/</span><span class="pln">privkey</span><span class="pun">.</span><span class="pln">pem</span><span class="pun">;</span><span class="pln">

    ssl_session_cache shared</span><span class="pun">:</span><span class="pln">le_nginx_SSL</span><span class="pun">:</span><span class="lit">10m</span><span class="pun">;</span><span class="pln">
    ssl_session_timeout </span><span class="lit">1440m</span><span class="pun">;</span><span class="pln">
    ssl_session_tickets off</span><span class="pun">;</span><span class="pln">

    ssl_protocols </span><span class="typ">TLSv1</span><span class="pun">.</span><span class="lit">2</span><span class="pln"> </span><span class="typ">TLSv1</span><span class="pun">.</span><span class="lit">3</span><span class="pun">;</span><span class="pln">
    ssl_prefer_server_ciphers off</span><span class="pun">;</span><span class="pln">

    ssl_ciphers </span><span class="str">"ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384"</span><span class="pun">;</span><span class="pln">

    client_max_body_size </span><span class="lit">4G</span><span class="pun">;</span><span class="pln">
    keepalive_timeout </span><span class="lit">5</span><span class="pun">;</span><span class="pln">

        location </span><span class="pun">/</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
          proxy_set_header X</span><span class="pun">-</span><span class="typ">Forwarded</span><span class="pun">-</span><span class="typ">For</span><span class="pln"> $proxy_add_x_forwarded_for</span><span class="pun">;</span><span class="pln">
          proxy_set_header X</span><span class="pun">-</span><span class="typ">Forwarded</span><span class="pun">-</span><span class="typ">Proto</span><span class="pln"> $scheme</span><span class="pun">;</span><span class="pln">
          proxy_set_header </span><span class="typ">Host</span><span class="pln"> $http_host</span><span class="pun">;</span><span class="pln">
          proxy_redirect off</span><span class="pun">;</span><span class="pln">
          proxy_pass http</span><span class="pun">://</span><span class="pln">django</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">

    location </span><span class="pun">^~</span><span class="pln"> </span><span class="pun">/.</span><span class="pln">well</span><span class="pun">-</span><span class="pln">known</span><span class="pun">/</span><span class="pln">acme</span><span class="pun">-</span><span class="pln">challenge</span><span class="pun">/</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        root </span><span class="pun">/</span><span class="pln">var</span><span class="pun">/</span><span class="pln">www</span><span class="pun">/</span><span class="pln">html</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

</span><span class="pun">}</span></pre>

<p>
	جهزنا الإعدادات السابقة بالاعتماد على عدة مصادر تتحدث عن إعداد Nginx مثل <a href="https://github.com/certbot/certbot/blob/master/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx.conf" rel="external nofollow">GitHub\Certbot</a> و <a href="https://docs.gunicorn.org/en/stable/deploy.html" rel="external nofollow">Gunicorn</a> و <a href="https://hub.docker.com/_/nginx" rel="external nofollow">Docker\Nginx</a> ودراستها وتعديلها لتناسب البيئة، هذه العملية في الواقع خارج نطاق مقالنا لذا ننصحك بالاطلاع على المقال <a href="https://academy.hsoub.com/devops/servers/web/nginx/%D9%81%D9%87%D9%85-%D8%A8%D9%86%D9%8A%D8%A9-%D9%85%D9%84%D9%81-%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF%D8%A7%D8%AA-nginx-%D9%88%D8%B3%D9%8A%D8%A7%D9%82%D8%A7%D8%AA-%D8%A7%D9%84%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF%D8%A7%D8%AA-r112/" rel="">فهم بنية ملف إعدادات nginx وسياقات الإعدادات</a> من أكاديمية حسوب والمقال الذي يتحدث عن <a href="https://www.digitalocean.com/community/tutorials/understanding-nginx-server-and-location-block-selection-algorithms" rel="external nofollow">خوارزميات اختيار كتل Server و Location ضمن الملف</a> من DigitalOcean لتفهم المزيد عن الإعدادات، أو الاستعانة ببعض الأدوات الخاصة بتجهيز ملف Nginx مثل <a href="https://www.digitalocean.com/community/tools/nginx" rel="external nofollow">NGINXConfig</a> على سبيل المثال لا الحصر.
</p>

<p>
	لنشرح الآن دلالات هذه الإعدادات وكيفية عملها حيث أنها تتضمن السياقات الرئيسية <code>upstream</code> و <code>server</code> و <code>location</code> وهي تُعنى بإعادة توجيه طلبات HTTP إلى HTTPS ومن ثم إيصالها إلى خادمي التطبيق مع موازنة الحمل بينهما.
</p>

<p>
	سياقات الملف conf/nginx.conf بالترتيب هي: سياق الخوادم العليا <code>upstream</code> وهو وحيد، يليه ثلاثة سياقات متتالية تسمى سياق الخادم server، وأخيرًا سياق الموقع <code>location</code> وهو مكرر مرتين متتاليتين في نهاية الملف.
</p>

<p>
	يحدد سياق الخوادم العليا <code>upstream</code> الخوادم التي ستتلقى طلبات الوكيل عبر عملية التوجيه <code>proxy_pass</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_3867_86" style="">
<span class="pln">upstream django </span><span class="pun">{</span><span class="pln">
    server APP_SERVER_1_IP</span><span class="pun">;</span><span class="pln">
    server APP_SERVER_2_IP</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span></pre>

<p>
	أطلقنا الاسم <code>django</code> على هذا السياق وعرفنا ضمنه خادمي التطبيق عبر كتابة عنوان الـ IP الخاص بكل خادم، أما في حال كنت تستخدم شبكة سحابية افتراضية خاصة VPC للربط بين الخادمين فاستبدل هذه العناوين بعناوين الخوادم الخاصة Private IP وفق إعدادات الشبكة VPC، ويمكنك الاستعانة <a href="https://docs.digitalocean.com/products/networking/vpc/how-to/enable/" rel="external nofollow">بالمثال</a> لمزيد من التفاصيل حول تفعيل هذه الشبكة.
</p>

<p>
	بعد توجيه الطلبات من سياق الخوادم العليا، يلتقط أول سياق خادم الطلبات التي لا تخص نطاقك وينهيها، وتعدّ طلبات HTTP المباشرة لخوادم التطبيق مثالًا على الحالات التي يتعامل معها هذا السياق:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3867_88" style="">
<span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln">
server </span><span class="pun">{</span><span class="pln">
    listen </span><span class="lit">80</span><span class="pln"> default_server</span><span class="pun">;</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="lit">444</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span></pre>

<p>
	أما ثاني سياق من نوع خادم فهو يتلقى الطلبات الواردة إلى نطاقك عبر HTTP ويعيد توجيهها إلى HTTPS عبر <a href="https://academy.hsoub.com/questions/18357-%D9%85%D8%A7%D8%B0%D8%A7-%D8%AA%D8%B9%D9%86%D9%8A-%D8%AD%D8%A7%D9%84%D8%A9-http-status-codes-3xx/" rel="">توجيه HTTP 301</a>، ليعالجها بعد ذلك سياق الخادم الأخير:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_3867_90" style="">
<span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln">
server </span><span class="pun">{</span><span class="pln">
    listen </span><span class="lit">80</span><span class="pun">;</span><span class="pln">
    listen </span><span class="pun">[::]:</span><span class="lit">80</span><span class="pun">;</span><span class="pln">
    server_name your_domain</span><span class="pun">.</span><span class="pln">com</span><span class="pun">;</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="lit">301</span><span class="pln"> https</span><span class="pun">:</span><span class="com">//$server_name$request_uri;</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span></pre>

<p>
	تحدد أسطر التوجيه التالية مسارات شهادة <abbr title="Transport Layer Security | بروتوكول أمن طبقة النقل">TLS</abbr> والمفتاح الخاص Secret Key، علمًا أن Certbot سيؤمن هذه الملفات، ومن ثم ستُوصل إلى داخل الحاوية عبر التعليمة <code>mount</code> كما سنرى في المراحل التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3867_92" style="">
<span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln">
ssl_certificate </span><span class="pun">/</span><span class="pln">etc</span><span class="pun">/</span><span class="pln">letsencrypt</span><span class="pun">/</span><span class="pln">live</span><span class="pun">/</span><span class="pln">your_domain</span><span class="pun">.</span><span class="pln">com</span><span class="pun">/</span><span class="pln">fullchain</span><span class="pun">.</span><span class="pln">pem</span><span class="pun">;</span><span class="pln">
ssl_certificate_key </span><span class="pun">/</span><span class="pln">etc</span><span class="pun">/</span><span class="pln">letsencrypt</span><span class="pun">/</span><span class="pln">live</span><span class="pun">/</span><span class="pln">your_domain</span><span class="pun">.</span><span class="pln">com</span><span class="pun">/</span><span class="pln">privkey</span><span class="pun">.</span><span class="pln">pem</span><span class="pun">;</span><span class="pln">
</span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span></pre>

<p>
	أما محددات اتصال <abbr title="Secure Socket Layer | طبقة المنافذ الآمنة">SSL</abbr> فتُضبط وفقًا للقيم الافتراضية التي توصي بها Certbot كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3867_94" style="">
<span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln">
    ssl_session_cache shared</span><span class="pun">:</span><span class="pln">le_nginx_SSL</span><span class="pun">:</span><span class="lit">10m</span><span class="pun">;</span><span class="pln">
    ssl_session_timeout </span><span class="lit">1440m</span><span class="pun">;</span><span class="pln">
    ssl_session_tickets off</span><span class="pun">;</span><span class="pln">

    ssl_protocols </span><span class="typ">TLSv1</span><span class="pun">.</span><span class="lit">2</span><span class="pln"> </span><span class="typ">TLSv1</span><span class="pun">.</span><span class="lit">3</span><span class="pun">;</span><span class="pln">
    ssl_prefer_server_ciphers off</span><span class="pun">;</span><span class="pln">

    ssl_ciphers </span><span class="str">"ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384"</span><span class="pun">;</span><span class="pln">
</span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span></pre>

<p>
	إن رغبت بمعلوماتٍ إضافية عن هذه المحددات وطريقة ضبطها فإننا نرشّح لك مقالين حول الموضوع من <a href="https://nginx.org/en/docs/http/ngx_http_ssl_module.html" rel="external nofollow">Nginx</a> و <a href="https://wiki.mozilla.org/Security/Server_Side_TLS" rel="external nofollow">Mozilla</a>.
</p>

<p>
	استرشدنا بتوثيق Gunicorn الخاص <a href="https://docs.gunicorn.org/en/stable/deploy.html" rel="external nofollow">بإعدادات Nginx</a> لضبط عدد الاتصالات الأعظمي المسموح به <code>client_max_body_size</code> وزمن جلسة الاتصال <code>keepalive_timeout</code> وهي المدة الزمنية العظمى التي يتم بعدها قطع الاتصال مع العميل وتقدر بالثانية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3867_96" style="">
<span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln">
client_max_body_size </span><span class="lit">4G</span><span class="pun">;</span><span class="pln">
keepalive_timeout </span><span class="lit">5</span><span class="pun">;</span><span class="pln">
</span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span></pre>

<p>
	لننتقل الآن للسياقات الأخيرة ضمن الملف وهي سياقات الموقع.
</p>

<p>
	يوجه أول سياق موقع Nginx أثناء تلقيمه وعكسه الطلبات إلى خوادم التطبيق المعرّفة ضمن سياق الخوادم العليا <code>upstream</code> وذلك عبر بروتوكول HTTP، مع المحافظة على معلومات ترويسة HTTP لاتصال العميل مثل عنوان الـ IP الأصلي وبروتوكول الاتصال المستخدم وعنوان الهدف:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3867_98" style="">
<span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln">
location </span><span class="pun">/</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    proxy_set_header X</span><span class="pun">-</span><span class="typ">Forwarded</span><span class="pun">-</span><span class="typ">For</span><span class="pln"> $proxy_add_x_forwarded_for</span><span class="pun">;</span><span class="pln">
    proxy_set_header X</span><span class="pun">-</span><span class="typ">Forwarded</span><span class="pun">-</span><span class="typ">Proto</span><span class="pln"> $scheme</span><span class="pun">;</span><span class="pln">
    proxy_set_header </span><span class="typ">Host</span><span class="pln"> $http_host</span><span class="pun">;</span><span class="pln">
    proxy_redirect off</span><span class="pun">;</span><span class="pln">
    proxy_pass http</span><span class="pun">://</span><span class="pln">django</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span></pre>

<p>
	اطلع على توثيق <a href="https://docs.gunicorn.org/en/stable/deploy.html#nginx-configuration" rel="external nofollow">Gunicorn</a> وتوثيق <a href="http://nginx.org/en/docs/http/ngx_http_proxy_module.html" rel="external nofollow">النموذج ngx_http_proxy_module</a> لمزيدٍ من المعلومات.
</p>

<p>
	يُسجل سياق الموقع الثاني الطلبات ضمن المسار /well-known/acme-challenge/ لاستخدامها من قبل Certbot للتحقق من اسم النطاق وفق تحدي HTTP-01 وهو أحد <a href="https://letsencrypt.org/docs/challenge-types/" rel="external nofollow">أنواع تحديات Let’s Encrypt</a> التي تستخدمها للاختبار والتحقق.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_3867_100" style="">
<span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln">
location </span><span class="pun">^~</span><span class="pln"> </span><span class="pun">/.</span><span class="pln">well</span><span class="pun">-</span><span class="pln">known</span><span class="pun">/</span><span class="pln">acme</span><span class="pun">-</span><span class="pln">challenge</span><span class="pun">/</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        root </span><span class="pun">/</span><span class="kwd">var</span><span class="pun">/</span><span class="pln">www</span><span class="pun">/</span><span class="pln">html</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	أغلق الآن ملف الإعدادات بعد حفظ التغييرات، حيث أنه سيُستخدم لتشغيل الحاوية.
</p>

<p>
	لننفذ الآن أمر تشغيل الحاوية وفق الصيغة التالية، بالاعتماد على الصورة nginx:1.19.0 وهي إحدى <a href="https://hub.docker.com/_/nginx" rel="external nofollow">صور دوكر الرسمية</a> المخصصة لخادم Nginx:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3867_102" style="">
<span class="pln">docker run </span><span class="pun">--</span><span class="pln">rm </span><span class="pun">--</span><span class="pln">name nginx </span><span class="pun">-</span><span class="pln">p </span><span class="lit">80</span><span class="pun">:</span><span class="lit">80</span><span class="pln"> </span><span class="pun">-</span><span class="pln">p </span><span class="lit">443</span><span class="pun">:</span><span class="lit">443</span><span class="pln"> \
    </span><span class="pun">-</span><span class="pln">v </span><span class="pun">~/</span><span class="pln">conf</span><span class="pun">/</span><span class="pln">nginx</span><span class="pun">.</span><span class="pln">conf</span><span class="pun">:/</span><span class="pln">etc</span><span class="pun">/</span><span class="pln">nginx</span><span class="pun">/</span><span class="pln">conf</span><span class="pun">.</span><span class="pln">d</span><span class="pun">/</span><span class="pln">nginx</span><span class="pun">.</span><span class="pln">conf</span><span class="pun">:</span><span class="pln">ro \
    </span><span class="pun">-</span><span class="pln">v </span><span class="pun">/</span><span class="pln">var</span><span class="pun">/</span><span class="pln">www</span><span class="pun">/</span><span class="pln">html</span><span class="pun">:/</span><span class="pln">var</span><span class="pun">/</span><span class="pln">www</span><span class="pun">/</span><span class="pln">html \
    nginx</span><span class="pun">:</span><span class="lit">1.19</span><span class="pun">.</span><span class="lit">0</span></pre>

<p>
	ستلاحظ أثناء التشغيل ظهور خطأ مفاده عدم وجود الشهادات <abbr title="Transport Layer Security | بروتوكول أمن طبقة النقل">TLS</abbr> المذكورة ضمن ملف الإعدادات nginx.conf، ومع ذلك سيكمل دوكر بناء الحاوية وتشغيلها، أما الخطأ سيُعالج في الخطوة التالية بتوفير الشهادات المطلوبة باستخدام Certbot و Let’s Encrypt.
</p>

<p>
	بالرجوع لأمر التشغيل سترى أننا أطلقنا الاسم nginx على الحاوية، وربطنا المنفذ 80 والمنفذ 443 للمضيف بالمنافذ التي تحمل الأرقام نفسها في الحاوية، واستخدمنا الراية <code>v-</code> لوصل ملف الإعدادات nginx.conf الذي ضبطناه مسبقًا إلى المسار etc/nginx/conf.d/nginx.conf/ داخل الحاوية بحيث تعتمده الصورة مع الانتباه إلى التعليمة تضمنت الراية <code>ro</code> التي تشير إلى "read only" أي أن الملف سيكون للقراءة فقط بالنسبة الحاوية فلا يمكن تعديله من داخلها، بالإضافة لذلك وصلنا مسار الجذر لتطبيق الويب إلى المسار var/www/html/ المقابل ضمن الحاوية، أما السطر الأخير في أمر التشغيل وهو <code>nginx:1.19.0</code> فسيرشد دوكر إلى الصورة المطلوبة على Dockerhub لاستخدامها.
</p>

<h2>
	الخطوة 4: إعداد Certbot وتجديد الشهادة من Let’s Encrypt
</h2>

<p>
	<a href="https://github.com/certbot/certbot" rel="external nofollow">Certbot</a> هو عميل <a href="https://letsencrypt.org/" rel="external nofollow">Let’s Encrypt</a> الأشهر طورته شركة <a href="https://www.eff.org/" rel="external nofollow">Electronic Frontier Foundation</a>، وظيفته تزويد خوادم الويب بشهادات <abbr title="Transport Layer Security | بروتوكول أمن طبقة النقل">TLS</abbr> مجانية مصدقة من Let’s Encrypt تعطي للمتصفحين الثقة بهوية الموقع.
</p>

<p>
	لنبدأ بالتطبيق العملي انطلاقًا من صورة Certbot المتوفرة على <a href="https://hub.docker.com/r/certbot/certbot/" rel="external nofollow">Dockerhub</a>، ولكن تأكد أولًا من وجود سجل DNS من النوع A يرتبط بعنوان Public IP للخادم الوكيل، ثم نفذ أمر التشغيل التالي ليوفر عميل certbot الشهادات اللازمة للاختبار staging مع خوادم Let’s Encrypt:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3867_105" style="">
<span class="pln">docker run </span><span class="pun">-</span><span class="pln">it </span><span class="pun">--</span><span class="pln">rm </span><span class="pun">-</span><span class="pln">p </span><span class="lit">80</span><span class="pun">:</span><span class="lit">80</span><span class="pln"> </span><span class="pun">--</span><span class="pln">name certbot \
         </span><span class="pun">-</span><span class="pln">v </span><span class="str">"/etc/letsencrypt:/etc/letsencrypt"</span><span class="pln"> \
         </span><span class="pun">-</span><span class="pln">v </span><span class="str">"/var/lib/letsencrypt:/var/lib/letsencrypt"</span><span class="pln"> \
         certbot</span><span class="pun">/</span><span class="pln">certbot certonly </span><span class="pun">--</span><span class="pln">standalone </span><span class="pun">--</span><span class="pln">staging </span><span class="pun">-</span><span class="pln">d your_domain</span><span class="pun">.</span><span class="pln">com</span></pre>

<p>
	يشغل الأمر السابق الصورة certbot في الوضع التفاعلي، ويربط البوابة 80 على الخادم المضيف مع البوابة 80 ضمن الحاوية، كما أنه ينشئ المسارين /etc/letsencrypt/ و /var/lib/letsencrypt/ ويوصلهما بنفس المسارات على المضيف.
</p>

<p>
	سيعمل Certbot بموجب الأمر السابق بالنمط المستقل أي بدون خادم Nginx ويتصل بخوادم Let’s Encrypt لإنجاز اختبار التشفير والتحقق من النطاق، وأثناء ذلك سيُطلب منك عنوان بريد إلكتروني وتأكيد الموافقة على شروط الخدمة، أدخل المطلوب وانتظر اكتمال العملية وفي حال نجح التحقق من نطاقك ستحصل على الخرج التالي الذي يخبرك بمكان تخزين الشهادة:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_3867_109" style="">
<span class="typ">Obtaining</span><span class="pln"> a </span><span class="kwd">new</span><span class="pln"> certificate
</span><span class="typ">Performing</span><span class="pln"> the following challenges</span><span class="pun">:</span><span class="pln">
http</span><span class="pun">-</span><span class="lit">01</span><span class="pln"> challenge </span><span class="kwd">for</span><span class="pln"> stubb</span><span class="pun">.</span><span class="pln">dev
</span><span class="typ">Waiting</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> verification</span><span class="pun">...</span><span class="pln">
</span><span class="typ">Cleaning</span><span class="pln"> up challenges

IMPORTANT NOTES</span><span class="pun">:</span><span class="pln">
 </span><span class="pun">-</span><span class="pln"> </span><span class="typ">Congratulations</span><span class="pun">!</span><span class="pln"> </span><span class="typ">Your</span><span class="pln"> certificate and chain have been saved at</span><span class="pun">:</span><span class="pln">
   </span><span class="str">/etc/</span><span class="pln">letsencrypt</span><span class="pun">/</span><span class="pln">live</span><span class="pun">/</span><span class="pln">your_domain</span><span class="pun">.</span><span class="pln">com</span><span class="pun">/</span><span class="pln">fullchain</span><span class="pun">.</span><span class="pln">pem
   </span><span class="typ">Your</span><span class="pln"> key file has been saved at</span><span class="pun">:</span><span class="pln">
   </span><span class="str">/etc/</span><span class="pln">letsencrypt</span><span class="pun">/</span><span class="pln">live</span><span class="pun">/</span><span class="pln">your_domain</span><span class="pun">.</span><span class="pln">com</span><span class="pun">/</span><span class="pln">privkey</span><span class="pun">.</span><span class="pln">pem
   </span><span class="typ">Your</span><span class="pln"> cert will expire on </span><span class="lit">2020</span><span class="pun">-</span><span class="lit">09</span><span class="pun">-</span><span class="lit">15.</span><span class="pln"> </span><span class="typ">To</span><span class="pln"> obtain a </span><span class="kwd">new</span><span class="pln"> or tweaked
   version of </span><span class="kwd">this</span><span class="pln"> certificate in the future</span><span class="pun">,</span><span class="pln"> simply run certbot
   again</span><span class="pun">.</span><span class="pln"> </span><span class="typ">To</span><span class="pln"> non</span><span class="pun">-</span><span class="pln">interactively renew </span><span class="pun">*</span><span class="pln">all</span><span class="pun">*</span><span class="pln"> of your certificates</span><span class="pun">,</span><span class="pln"> run
   </span><span class="str">"certbot renew"</span><span class="pln">
 </span><span class="pun">-</span><span class="pln"> </span><span class="typ">Your</span><span class="pln"> account credentials have been saved in your </span><span class="typ">Certbot</span><span class="pln">
   configuration directory at </span><span class="pun">/</span><span class="pln">etc</span><span class="pun">/</span><span class="pln">letsencrypt</span><span class="pun">.</span><span class="pln"> </span><span class="typ">You</span><span class="pln"> should make a
   secure backup of </span><span class="kwd">this</span><span class="pln"> folder now</span><span class="pun">.</span><span class="pln"> </span><span class="typ">This</span><span class="pln"> configuration directory will
   also contain certificates and </span><span class="kwd">private</span><span class="pln"> keys obtained by </span><span class="typ">Certbot</span><span class="pln"> so
   making regular backups of </span><span class="kwd">this</span><span class="pln"> folder is ideal</span><span class="pun">.</span></pre>

<p>
	يمكنك استعراض ملف الشهادة باستخدام <code>cat</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_3867_111" style="">
<span class="pln">sudo cat </span><span class="pun">/</span><span class="pln">etc</span><span class="pun">/</span><span class="pln">letsencrypt</span><span class="pun">/</span><span class="pln">live</span><span class="pun">/</span><span class="pln">your_domain</span><span class="pun">.</span><span class="pln">com</span><span class="pun">/</span><span class="pln">fullchain</span><span class="pun">.</span><span class="pln">pem</span></pre>

<p>
	سنعيد الآن تشغيل الحاوية nginx بنفس أمر التشغيل المستخدم في الخطوة /3/ ولكن مع إضافة مسارات Let’s Encrypt التي بنيناها توًا أثناء تشغيل حاوية certbot:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_3867_113" style="">
<span class="pln">docker run </span><span class="pun">--</span><span class="pln">rm </span><span class="pun">--</span><span class="pln">name nginx </span><span class="pun">-</span><span class="pln">p </span><span class="lit">80</span><span class="pun">:</span><span class="lit">80</span><span class="pln"> </span><span class="pun">-</span><span class="pln">p </span><span class="lit">443</span><span class="pun">:</span><span class="lit">443</span><span class="pln"> \
    </span><span class="pun">-</span><span class="pln">v </span><span class="pun">~</span><span class="str">/conf/</span><span class="pln">nginx</span><span class="pun">.</span><span class="pln">conf</span><span class="pun">:</span><span class="str">/etc/</span><span class="pln">nginx</span><span class="pun">/</span><span class="pln">conf</span><span class="pun">.</span><span class="pln">d</span><span class="pun">/</span><span class="pln">nginx</span><span class="pun">.</span><span class="pln">conf</span><span class="pun">:</span><span class="pln">ro \
    </span><span class="pun">-</span><span class="pln">v </span><span class="pun">/</span><span class="pln">etc</span><span class="pun">/</span><span class="pln">letsencrypt</span><span class="pun">:</span><span class="str">/etc/</span><span class="pln">letsencrypt \
    </span><span class="pun">-</span><span class="pln">v </span><span class="pun">/</span><span class="kwd">var</span><span class="pun">/</span><span class="pln">lib</span><span class="pun">/</span><span class="pln">letsencrypt</span><span class="pun">:</span><span class="str">/var/</span><span class="pln">lib</span><span class="pun">/</span><span class="pln">letsencrypt \
    </span><span class="pun">-</span><span class="pln">v </span><span class="pun">/</span><span class="kwd">var</span><span class="pun">/</span><span class="pln">www</span><span class="pun">/</span><span class="pln">html</span><span class="pun">:</span><span class="str">/var/</span><span class="pln">www</span><span class="pun">/</span><span class="pln">html \
    nginx</span><span class="pun">:</span><span class="lit">1.19</span><span class="pun">.</span><span class="lit">0</span></pre>

<p>
	فور إقلاع الحاوية nginx يمكنك أن تجرب فتح التطبيق من خلال المتصفح بوضع الرابط
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_3867_115" style="">
<span class="pln">http</span><span class="pun">:</span><span class="com">//your_domain.com</span></pre>

<p>
	وستلاحظ وجود تحذير أمني مفاده انتهاء صلاحية الشهادة وهذا متوقع لأننا ما زلنا نعمل على الشهادات المرحلية الاختبارية staging، ولكن مع ذلك ستجد أن طلبك قد تم إعادة توجيهه من HTTP إلى HTTPS.
</p>

<p>
	اضغط الآن على الاختصار Ctrl+c للخروج من حاوية Nginx وشغل عميل Cerbot مجددًا ولكن مع حذف الراية الخاص بالاختبار <code>staging--</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3867_117" style="">
<span class="pln">docker run </span><span class="pun">-</span><span class="pln">it </span><span class="pun">--</span><span class="pln">rm </span><span class="pun">-</span><span class="pln">p </span><span class="lit">80</span><span class="pun">:</span><span class="lit">80</span><span class="pln"> </span><span class="pun">--</span><span class="pln">name certbot \
         </span><span class="pun">-</span><span class="pln">v </span><span class="str">"/etc/letsencrypt:/etc/letsencrypt"</span><span class="pln"> \
         </span><span class="pun">-</span><span class="pln">v </span><span class="str">"/var/lib/letsencrypt:/var/lib/letsencrypt"</span><span class="pln"> \
         certbot</span><span class="pun">/</span><span class="pln">certbot certonly </span><span class="pun">--</span><span class="pln">standalone </span><span class="pun">-</span><span class="pln">d your_domain</span><span class="pun">.</span><span class="pln">com</span></pre>

<p>
	ستظهر لك رسالة لاختيار الإجراء الذي ترغب بتنفيذه تجديد الشهادة أو استبدالها، اضغط على الرقم <code>2</code> لخيار التجديد ومن ثم زر الإدخال Enter وستحصل بذلك على شهادة <abbr title="Transport Layer Security | بروتوكول أمن طبقة النقل">TLS</abbr> فعالة production، ثم شغل الآن حاوية nginx بالاعتماد على هذه الشهادة وفق التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3867_119" style="">
<span class="pln">docker run </span><span class="pun">--</span><span class="pln">rm </span><span class="pun">--</span><span class="pln">name nginx </span><span class="pun">-</span><span class="pln">p </span><span class="lit">80</span><span class="pun">:</span><span class="lit">80</span><span class="pln"> </span><span class="pun">-</span><span class="pln">p </span><span class="lit">443</span><span class="pun">:</span><span class="lit">443</span><span class="pln"> \
    </span><span class="pun">-</span><span class="pln">v </span><span class="pun">~/</span><span class="pln">conf</span><span class="pun">/</span><span class="pln">nginx</span><span class="pun">.</span><span class="pln">conf</span><span class="pun">:/</span><span class="pln">etc</span><span class="pun">/</span><span class="pln">nginx</span><span class="pun">/</span><span class="pln">conf</span><span class="pun">.</span><span class="pln">d</span><span class="pun">/</span><span class="pln">nginx</span><span class="pun">.</span><span class="pln">conf</span><span class="pun">:</span><span class="pln">ro \
    </span><span class="pun">-</span><span class="pln">v </span><span class="pun">/</span><span class="pln">etc</span><span class="pun">/</span><span class="pln">letsencrypt</span><span class="pun">:/</span><span class="pln">etc</span><span class="pun">/</span><span class="pln">letsencrypt \
    </span><span class="pun">-</span><span class="pln">v </span><span class="pun">/</span><span class="pln">var</span><span class="pun">/</span><span class="pln">lib</span><span class="pun">/</span><span class="pln">letsencrypt</span><span class="pun">:/</span><span class="pln">var</span><span class="pun">/</span><span class="pln">lib</span><span class="pun">/</span><span class="pln">letsencrypt \
    </span><span class="pun">-</span><span class="pln">v </span><span class="pun">/</span><span class="pln">var</span><span class="pun">/</span><span class="pln">www</span><span class="pun">/</span><span class="pln">html</span><span class="pun">:/</span><span class="pln">var</span><span class="pun">/</span><span class="pln">www</span><span class="pun">/</span><span class="pln">html \
    nginx</span><span class="pun">:</span><span class="lit">1.19</span><span class="pun">.</span><span class="lit">0</span></pre>

<p>
	استعرض التطبيق مجددًا بكتابة الرابط في المتصفح:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_3867_121" style="">
<span class="pln">http</span><span class="pun">:</span><span class="com">//your_domain.com</span></pre>

<p>
	لاحظ إعادة توجيه طلبك إلى HTTPS وظهور رسالة "لم يتم العثور على الصفحة" بسبب عدم إعداد توجيه افتراضي للتطبيق Polls، استخدم إذًا الرابط التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_3867_123" style="">
<span class="pln">https</span><span class="pun">:</span><span class="com">//your_domain.com/polls</span></pre>

<p>
	وذلك لاستعراض واجهة التطبيق التي تشبه الصورة التالية:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="111577" href="https://academy.hsoub.com/uploads/monthly_2022_11/01-img-polls_app.png.2127ceed42d9dbf77b01cc2ff5474e8b.png" rel=""><img alt="01-img-polls_app.png" class="ipsImage ipsImage_thumbnailed" data-fileid="111577" data-unique="l3ejufp95" src="https://academy.hsoub.com/uploads/monthly_2022_11/01-img-polls_app.png.2127ceed42d9dbf77b01cc2ff5474e8b.png"></a>
</p>

<p>
	زودنا تطبيقنا لغاية الآن بشهادة <abbr title="Transport Layer Security | بروتوكول أمن طبقة النقل">TLS</abbr> عبر حاوية Certbot ووكيل عكسي وموازن حمل لتوجيه وموازنة الطلبات الخارجية الواردة إلى خوادم التطبيق، و قبل الانتقال للخطوة /5/ الختامية يتبقى لدينا موضوع أخير وهو تجهيز آلية مناسبة لتجديد صلاحية <abbr title="Transport Layer Security | بروتوكول أمن طبقة النقل">TLS</abbr> حيث أن الشهادات التي تعطيها Let’s Encrypt تحتاج إلى تجديد دوري كل 90 يومًا.
</p>

<p>
	سنبدل أولًا نمط عمل Certbot من النمط المستقل <code>standalone</code> إلى النمط <code>webroot</code> ذلك أن بيئتنا تضمن خادم ويب Nginx، وهذا يعني أن عميل Certbot سينشئ الملف الخاص بعملية التحقق ضمن مسار الجذر لخادم الويب التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_3867_125" style="">
<span class="str">/var/</span><span class="pln">www</span><span class="pun">/</span><span class="pln">html</span><span class="pun">/.</span><span class="pln">well</span><span class="pun">-</span><span class="pln">known</span><span class="pun">/</span><span class="pln">acme</span><span class="pun">-</span><span class="pln">challenge</span><span class="pun">/</span></pre>

<p>
	كذلك ستصل طلبات التحقق من Let’s Encrypt لهذا المسار وفق قواعد التوجيه الموجودة في سياقات الموقع ضمن ملف إعدادات Nginx، وعند إتمام عملية التجديد سنحتاج إلى إعادة تحميل Nginx ليستخدم الشهادات الجديدة الصالحة.
</p>

<p>
	يلجأ المستخدمون عادةً إلى أتمتة هذه العملية حتى ينجزها Certbot تلقائيًا وذلك بعدة أساليب أشهرها إضافة العملية على قائمة المهام المجدولة لنظام التشغيل لينكس عبر تعليمة <code>cron</code>، يمكنك الاطلاع على هذه العملية ضمن مقالٍ آخر حول <a href="https://www.digitalocean.com/community/tutorials/how-to-secure-a-containerized-node-js-application-with-nginx-let-s-encrypt-and-docker-compose#step-6-renewing-certificates" rel="external nofollow">تأمين Node.js</a> من DigitalOcean.
</p>

<p>
	لننهي عمل حاوية Nginx بالضغط على الاختصار Ctrl+c ونعيد تشغيلها في الوضع المنفصل مع الراية <code>d-</code> وفق التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_3867_127" style="">
<span class="pln">docker run </span><span class="pun">--</span><span class="pln">rm </span><span class="pun">--</span><span class="pln">name nginx </span><span class="pun">-</span><span class="pln">d </span><span class="pun">-</span><span class="pln">p </span><span class="lit">80</span><span class="pun">:</span><span class="lit">80</span><span class="pln"> </span><span class="pun">-</span><span class="pln">p </span><span class="lit">443</span><span class="pun">:</span><span class="lit">443</span><span class="pln"> \
    </span><span class="pun">-</span><span class="pln">v </span><span class="pun">~</span><span class="str">/conf/</span><span class="pln">nginx</span><span class="pun">.</span><span class="pln">conf</span><span class="pun">:</span><span class="str">/etc/</span><span class="pln">nginx</span><span class="pun">/</span><span class="pln">conf</span><span class="pun">.</span><span class="pln">d</span><span class="pun">/</span><span class="pln">nginx</span><span class="pun">.</span><span class="pln">conf</span><span class="pun">:</span><span class="pln">ro \
    </span><span class="pun">-</span><span class="pln">v </span><span class="pun">/</span><span class="pln">etc</span><span class="pun">/</span><span class="pln">letsencrypt</span><span class="pun">:</span><span class="str">/etc/</span><span class="pln">letsencrypt \
    </span><span class="pun">-</span><span class="pln">v </span><span class="pun">/</span><span class="kwd">var</span><span class="pun">/</span><span class="pln">lib</span><span class="pun">/</span><span class="pln">letsencrypt</span><span class="pun">:</span><span class="str">/var/</span><span class="pln">lib</span><span class="pun">/</span><span class="pln">letsencrypt \
  </span><span class="pun">-</span><span class="pln">v </span><span class="pun">/</span><span class="kwd">var</span><span class="pun">/</span><span class="pln">www</span><span class="pun">/</span><span class="pln">html</span><span class="pun">:</span><span class="str">/var/</span><span class="pln">www</span><span class="pun">/</span><span class="pln">html \
    nginx</span><span class="pun">:</span><span class="lit">1.19</span><span class="pun">.</span><span class="lit">0</span></pre>

<p>
	بينما يعمل خادم Nginx في الخلفية، سنشغل الحاوية certbot مع كل من إضافة <code>webroot--</code> التي ستحدد مسار الجذر لخادم الويب، والراية <code>dry-run--</code> لمحاكاة عملية التجديد والتأكد أن كافة الخيارات صحيحة دون القيام بالتجديد فعلًا:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_3867_129" style="">
<span class="pln">docker run </span><span class="pun">-</span><span class="pln">it </span><span class="pun">--</span><span class="pln">rm </span><span class="pun">--</span><span class="pln">name certbot \
    </span><span class="pun">-</span><span class="pln">v </span><span class="str">"/etc/letsencrypt:/etc/letsencrypt"</span><span class="pln"> \
  </span><span class="pun">-</span><span class="pln">v </span><span class="str">"/var/lib/letsencrypt:/var/lib/letsencrypt"</span><span class="pln"> \
  </span><span class="pun">-</span><span class="pln">v </span><span class="str">"/var/www/html:/var/www/html"</span><span class="pln"> \
  certbot</span><span class="pun">/</span><span class="pln">certbot renew </span><span class="pun">--</span><span class="pln">webroot </span><span class="pun">-</span><span class="pln">w </span><span class="pun">/</span><span class="kwd">var</span><span class="pun">/</span><span class="pln">www</span><span class="pun">/</span><span class="pln">html </span><span class="pun">--</span><span class="pln">dry</span><span class="pun">-</span><span class="pln">run</span></pre>

<p>
	وفي حال كانت المحاكاة سليمة ستحصل على الخرج التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3867_131" style="">
<span class="typ">Cert</span><span class="pln"> </span><span class="kwd">not</span><span class="pln"> due </span><span class="kwd">for</span><span class="pln"> renewal</span><span class="pun">,</span><span class="pln"> but simulating renewal </span><span class="kwd">for</span><span class="pln"> dry run
</span><span class="typ">Plugins</span><span class="pln"> selected</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Authenticator</span><span class="pln"> webroot</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Installer</span><span class="pln"> </span><span class="kwd">None</span><span class="pln">
</span><span class="typ">Renewing</span><span class="pln"> an existing certificate
</span><span class="typ">Performing</span><span class="pln"> the following challenges</span><span class="pun">:</span><span class="pln">
http</span><span class="pun">-</span><span class="lit">01</span><span class="pln"> challenge </span><span class="kwd">for</span><span class="pln"> your_domain</span><span class="pun">.</span><span class="pln">com
</span><span class="typ">Using</span><span class="pln"> the webroot path </span><span class="pun">/</span><span class="pln">var</span><span class="pun">/</span><span class="pln">www</span><span class="pun">/</span><span class="pln">html </span><span class="kwd">for</span><span class="pln"> all unmatched domains</span><span class="pun">.</span><span class="pln">
</span><span class="typ">Waiting</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> verification</span><span class="pun">...</span><span class="pln">
</span><span class="typ">Cleaning</span><span class="pln"> up challenges

</span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln">
new certificate deployed without reload</span><span class="pun">,</span><span class="pln"> fullchain </span><span class="kwd">is</span><span class="pln">
</span><span class="pun">/</span><span class="pln">etc</span><span class="pun">/</span><span class="pln">letsencrypt</span><span class="pun">/</span><span class="pln">live</span><span class="pun">/</span><span class="pln">your_domain</span><span class="pun">.</span><span class="pln">com</span><span class="pun">/</span><span class="pln">fullchain</span><span class="pun">.</span><span class="pln">pem
</span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln">

</span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln">
</span><span class="pun">**</span><span class="pln"> DRY RUN</span><span class="pun">:</span><span class="pln"> simulating </span><span class="str">'certbot renew'</span><span class="pln"> close to cert expiry
</span><span class="pun">**</span><span class="pln">          </span><span class="pun">(</span><span class="typ">The</span><span class="pln"> test certificates below have </span><span class="kwd">not</span><span class="pln"> been saved</span><span class="pun">.)</span><span class="pln">

</span><span class="typ">Congratulations</span><span class="pun">,</span><span class="pln"> all renewals succeeded</span><span class="pun">.</span><span class="pln"> </span><span class="typ">The</span><span class="pln"> following certs have been renewed</span><span class="pun">:</span><span class="pln">
  </span><span class="pun">/</span><span class="pln">etc</span><span class="pun">/</span><span class="pln">letsencrypt</span><span class="pun">/</span><span class="pln">live</span><span class="pun">/</span><span class="pln">your_domain</span><span class="pun">.</span><span class="pln">com</span><span class="pun">/</span><span class="pln">fullchain</span><span class="pun">.</span><span class="pln">pem </span><span class="pun">(</span><span class="pln">success</span><span class="pun">)</span><span class="pln">
</span><span class="pun">**</span><span class="pln"> DRY RUN</span><span class="pun">:</span><span class="pln"> simulating </span><span class="str">'certbot renew'</span><span class="pln"> close to cert expiry
</span><span class="pun">**</span><span class="pln">          </span><span class="pun">(</span><span class="typ">The</span><span class="pln"> test certificates above have </span><span class="kwd">not</span><span class="pln"> been saved</span><span class="pun">.)</span><span class="pln">
</span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span></pre>

<p>
	يتوجب علينا بعد ذلك إعادة تحميل Nginx ليستخدم الشهادة المجددة وذلك وفق الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3867_133" style="">
<span class="pln">docker kill </span><span class="pun">-</span><span class="pln">s HUP nginx</span></pre>

<p>
	حيث ترسل التعليمة <a href="https://academy.hsoub.com/devops/servers/web/nginx/%D9%83%D9%8A%D9%81-%D8%AA%D9%8F%D8%B1%D9%82%D9%91%D9%90%D9%8A-%D8%AE%D8%A7%D8%AF%D9%85-nginx-%D9%85%D9%88%D8%AC%D9%88%D8%AF-%D8%A8%D8%AF%D9%88%D9%86-%D9%82%D8%B7%D8%B9-%D8%A7%D8%AA%D8%B5%D8%A7%D9%84%D8%A7%D8%AA-%D8%A7%D9%84%D8%B9%D9%85%D9%8A%D9%84-r445/" rel="">HUP</a> إشارة إلى خادم Nginx داخل الحاوية ليعيد تحميل الإعدادات ويستخدم الإعدادات الجديدة بما فيها الشهادات المجددة.
</p>

<p>
	لننتقل الآن للخطوة الأخيرة وهي حماية خوادم الواجهات الخلفية للتطبيق عبر تقيّد الاتصالات الخارجية معها بالخادم الوكيل فقط.
</p>

<h2>
	الخطوة 5: منع الوصول الخارجي إلى خادمي تطبيق جانغو
</h2>

<p>
	في البنية الموصوفة في هذا المقال يتولى خادم Nginx فك تشفير اتصالات <abbr title="Secure Socket Layer | طبقة المنافذ الآمنة">SSL</abbr> ويوصل البيانات بدون تشفير إلى خوادم جانغو، تعدّ هذه الآلية مناسبة في العديد من الحالات إلا أن بعض التطبيقات (المالية مثلًا) قد تتطلب مستويات أمان أعلى فيعتمد مشغلوّها تقنية التشفير طرف إلى طرف end-to-end، وهو ما يمكنك تنفيذه بإيصال حزم البيانات مشفرة إلى خوادم التطبيق ليُفك تشفيرها هناك، أو بإعادة تشفيرها ثانيةً عند الخادم الوكيل وفك التشفير مجددًا على خوادم تطبيق جانغو، لن نتناول هذه التقنية من التشفير في مقالنا ولكن يمكنك الحصول على المزيد من المعلومات حولها بمراجعة <a href="https://ar.wikipedia.org/wiki/%D8%AA%D8%B9%D9%85%D9%8A%D8%A9_%D8%A8%D9%8A%D9%86_%D8%A7%D9%84%D8%B7%D8%B1%D9%81%D9%8A%D8%A7%D8%AA" rel="external nofollow">التشفير طرف-إلى-طرف</a>.
</p>

<p>
	إذًا الخادم Nginx في بنيتنا هو بوابة العبور بين الاتصالات الخارجية وخادمي جانغو ومن المفترض نظريًا أن لا يتصل أي عميل مع خوادم التطبيق إلا عبر هذه البوابة، ولكن تذكر <a href="https://github.com/docker/for-linux/issues/690" rel="external nofollow">الثغرة الأمنية المفتوحة</a> التي واجهتنا في الخطوة /1/ حيث تمكن دوكر من تجاوز الجدار الناري UFW وفتح بوابات خارجية دون إعداد أي سياسات صريحة لفتح هذه البوابات على الجدار الناري.
</p>

<p>
	سنعالج هذه الثغرة عبر تعديل إعدادات الجدار الناري غير المعقد UFW مع العلم أن بإمكانك معالجتها بتعديل iptables الخاصة بنظام التشغيل مباشرةً بحيث تمنع الوصول غير المصرح به للحاوية دوكر، ويمكنك تعلم المزيد من مقال <a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D8%A7%D9%84%D8%AA%D9%91%D8%B4%D8%A8%D9%8A%D9%83-%D9%88%D8%A7%D9%84%D8%AA%D9%91%D9%88%D8%A7%D8%B5%D9%8F%D9%84-%D8%B9%D9%84%D9%89-docker-r37/" rel="">التّشبيك والتّواصُل على Docker</a> ومقال <a href="https://academy.hsoub.com/devops/security/firewalls/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-iptables-%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D9%88%D8%A3%D9%88%D8%A7%D9%85%D8%B1-%D8%B4%D8%A7%D8%A6%D8%B9%D8%A9-%D9%84%D9%84%D8%AC%D8%AF%D8%A7%D8%B1-%D8%A7%D9%84%D9%86%D8%A7%D8%B1%D9%8A-r119/" rel="">أساسيات IPTables - قواعد وأوامر شائعة للجدار الناري</a>، لكننا ننصحك باستخدام جدران نارية أكثر تطورًا في البيئة الحقيقية كالجدران السحابية مثلًا.
</p>

<p>
	لنبدأ الآن بتعديل إعدادات الجدار الناري UFW المشروحة في مستودع <a href="https://github.com/chaifeng/ufw-docker" rel="external nofollow">ufw-docker</a> في GitHub لمنع الاتصالات الخارجية مع بوابات المضيف التي فتحها دوكر، فقد مررنا الراية <code>p 80:8000-</code> لحاويات خوادم التطبيق أثناء تشغيلها، ما يعني أن البوابة 80 على المضيف فُتحت ووُجّهت إلى البوابة 8000 على الحاوية وهي بذلك أصبحت متاحة أمام الاتصالات الخارجية أيضًا حيث أنها تمكننا من فتح الرابط:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_3867_135" style="">
<span class="pln">http</span><span class="pun">:</span><span class="com">//your_app_server_1_IP</span></pre>

<p>
	سجل الدخول إلى خادم التطبيق الأول وافتح الملف <code>etc/ufw/after.rules/</code> بامتيازات sudo:
</p>

<pre class="ipsCode">
sudo nano /etc/ufw/after.rules
</pre>

<p>
	أدخل كلمة المرور واضغط زر الإدخال Enter وستظهر لك محتويات الملف after.rules وهي الإعدادات الخاصة بالجدار الناري UFW:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3867_139" style="">
<span class="com">#</span><span class="pln">
</span><span class="com"># rules.input-after</span><span class="pln">
</span><span class="com">#</span><span class="pln">
</span><span class="com"># Rules that should be run after the ufw command line added rules. Custom</span><span class="pln">
</span><span class="com"># rules should be added to one of these chains:</span><span class="pln">
</span><span class="com">#   ufw-after-input</span><span class="pln">
</span><span class="com">#   ufw-after-output</span><span class="pln">
</span><span class="com">#   ufw-after-forward</span><span class="pln">
</span><span class="com">#</span><span class="pln">

</span><span class="com"># Don't delete these required lines, otherwise there will be errors</span><span class="pln">
</span><span class="pun">*</span><span class="pln">filter
</span><span class="pun">:</span><span class="pln">ufw</span><span class="pun">-</span><span class="pln">after</span><span class="pun">-</span><span class="pln">input </span><span class="pun">-</span><span class="pln"> </span><span class="pun">[</span><span class="lit">0</span><span class="pun">:</span><span class="lit">0</span><span class="pun">]</span><span class="pln">
</span><span class="pun">:</span><span class="pln">ufw</span><span class="pun">-</span><span class="pln">after</span><span class="pun">-</span><span class="pln">output </span><span class="pun">-</span><span class="pln"> </span><span class="pun">[</span><span class="lit">0</span><span class="pun">:</span><span class="lit">0</span><span class="pun">]</span><span class="pln">
</span><span class="pun">:</span><span class="pln">ufw</span><span class="pun">-</span><span class="pln">after</span><span class="pun">-</span><span class="pln">forward </span><span class="pun">-</span><span class="pln"> </span><span class="pun">[</span><span class="lit">0</span><span class="pun">:</span><span class="lit">0</span><span class="pun">]</span><span class="pln">
</span><span class="com"># End required lines</span><span class="pln">

</span><span class="com"># don't log noisy services by default</span><span class="pln">
</span><span class="pun">-</span><span class="pln">A ufw</span><span class="pun">-</span><span class="pln">after</span><span class="pun">-</span><span class="pln">input </span><span class="pun">-</span><span class="pln">p udp </span><span class="pun">--</span><span class="pln">dport </span><span class="lit">137</span><span class="pln"> </span><span class="pun">-</span><span class="pln">j ufw</span><span class="pun">-</span><span class="pln">skip</span><span class="pun">-</span><span class="pln">to</span><span class="pun">-</span><span class="pln">policy</span><span class="pun">-</span><span class="pln">input
</span><span class="pun">-</span><span class="pln">A ufw</span><span class="pun">-</span><span class="pln">after</span><span class="pun">-</span><span class="pln">input </span><span class="pun">-</span><span class="pln">p udp </span><span class="pun">--</span><span class="pln">dport </span><span class="lit">138</span><span class="pln"> </span><span class="pun">-</span><span class="pln">j ufw</span><span class="pun">-</span><span class="pln">skip</span><span class="pun">-</span><span class="pln">to</span><span class="pun">-</span><span class="pln">policy</span><span class="pun">-</span><span class="pln">input
</span><span class="pun">-</span><span class="pln">A ufw</span><span class="pun">-</span><span class="pln">after</span><span class="pun">-</span><span class="pln">input </span><span class="pun">-</span><span class="pln">p tcp </span><span class="pun">--</span><span class="pln">dport </span><span class="lit">139</span><span class="pln"> </span><span class="pun">-</span><span class="pln">j ufw</span><span class="pun">-</span><span class="pln">skip</span><span class="pun">-</span><span class="pln">to</span><span class="pun">-</span><span class="pln">policy</span><span class="pun">-</span><span class="pln">input
</span><span class="pun">-</span><span class="pln">A ufw</span><span class="pun">-</span><span class="pln">after</span><span class="pun">-</span><span class="pln">input </span><span class="pun">-</span><span class="pln">p tcp </span><span class="pun">--</span><span class="pln">dport </span><span class="lit">445</span><span class="pln"> </span><span class="pun">-</span><span class="pln">j ufw</span><span class="pun">-</span><span class="pln">skip</span><span class="pun">-</span><span class="pln">to</span><span class="pun">-</span><span class="pln">policy</span><span class="pun">-</span><span class="pln">input
</span><span class="pun">-</span><span class="pln">A ufw</span><span class="pun">-</span><span class="pln">after</span><span class="pun">-</span><span class="pln">input </span><span class="pun">-</span><span class="pln">p udp </span><span class="pun">--</span><span class="pln">dport </span><span class="lit">67</span><span class="pln"> </span><span class="pun">-</span><span class="pln">j ufw</span><span class="pun">-</span><span class="pln">skip</span><span class="pun">-</span><span class="pln">to</span><span class="pun">-</span><span class="pln">policy</span><span class="pun">-</span><span class="pln">input
</span><span class="pun">-</span><span class="pln">A ufw</span><span class="pun">-</span><span class="pln">after</span><span class="pun">-</span><span class="pln">input </span><span class="pun">-</span><span class="pln">p udp </span><span class="pun">--</span><span class="pln">dport </span><span class="lit">68</span><span class="pln"> </span><span class="pun">-</span><span class="pln">j ufw</span><span class="pun">-</span><span class="pln">skip</span><span class="pun">-</span><span class="pln">to</span><span class="pun">-</span><span class="pln">policy</span><span class="pun">-</span><span class="pln">input

</span><span class="com"># don't log noisy broadcast</span><span class="pln">
</span><span class="pun">-</span><span class="pln">A ufw</span><span class="pun">-</span><span class="pln">after</span><span class="pun">-</span><span class="pln">input </span><span class="pun">-</span><span class="pln">m addrtype </span><span class="pun">--</span><span class="pln">dst</span><span class="pun">-</span><span class="pln">type BROADCAST </span><span class="pun">-</span><span class="pln">j ufw</span><span class="pun">-</span><span class="pln">skip</span><span class="pun">-</span><span class="pln">to</span><span class="pun">-</span><span class="pln">policy</span><span class="pun">-</span><span class="pln">input

</span><span class="com"># don't delete the 'COMMIT' line or these rules won't be processed</span><span class="pln">
COMMIT</span></pre>

<p>
	الصق التعليمات التالية في نهاية الملف السابق (ومصدرها مستودع ufw-docker كما ذكرنا):
</p>

<pre class="ipsCode">
. . .

# BEGIN UFW AND DOCKER
*filter
:ufw-user-forward - [0:0]
:DOCKER-USER - [0:0]
-A DOCKER-USER -j RETURN -s 10.0.0.0/8
-A DOCKER-USER -j RETURN -s 172.16.0.0/12
-A DOCKER-USER -j RETURN -s 192.168.0.0/16

-A DOCKER-USER -p udp -m udp --sport 53 --dport 1024:65535 -j RETURN

-A DOCKER-USER -j ufw-user-forward

-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 192.168.0.0/16
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 10.0.0.0/8
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 172.16.0.0/12
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 192.168.0.0/16
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 10.0.0.0/8
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 172.16.0.0/12

-A DOCKER-USER -j RETURN
COMMIT
# END UFW AND DOCKER
</pre>

<p>
	تُقيّد التعليمات السابقة الوصول العام إلى البوابات التي فتحها دوكر وتسمح بالوصول لعناوين الشبكة الخاصة فقط وهي هنا 10.0.0.0/8 و 172.16.0.0/12 و 192.168.0.0/16 في حال كنت تستخدم الشبكات الخاصة مثل <a href="https://docs.digitalocean.com/products/networking/vpc/" rel="external nofollow">VPC</a> وبذلك فإن Droplets البوابة الخاصة بهذه الشبكة ستتمكن من الوصول إلى البوابات المفتوحة بينما تُمنع المصادر الخارجية من ذلك، لتعرف المزيد عن إعدادات UFW في هذه الحالة انظر <a href="https://github.com/chaifeng/ufw-docker#how-it-works" rel="external nofollow">ufw-docker how-it-works</a>.
</p>

<p>
	وفي حال أنك وضعت العناوين العامة Public IP لكل من خادمي التطبيق ضمن سياق الخوادم العليا في ملف إعدادات Nginx ولم تعتمد VPC، فعليك إذًا تعديل إعدادات UFW والسماح صراحةً بحركة البيانات القادمة من الخادم الوكيل Nginx إلى خوادم التطبيق عبر البوابة 80 وذلك عبر إنشاء سياسة خاصة بذلك باستخدام التعليمة <code>allow</code>، استعن لكتابتها بالمقال <a href="https://academy.hsoub.com/devops/security/firewalls/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-iptables-%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D9%88%D8%A3%D9%88%D8%A7%D9%85%D8%B1-%D8%B4%D8%A7%D8%A6%D8%B9%D8%A9-%D9%84%D9%84%D8%AC%D8%AF%D8%A7%D8%B1-%D8%A7%D9%84%D9%86%D8%A7%D8%B1%D9%8A-r119/" rel="">أساسيات IPTables - قواعد وأوامر شائعة للجدار الناري</a>، واحفظ بعدها التغيرات وأغلق الملف.
</p>

<p>
	أعد تشغيل الجدار الناري <code>ufw</code> ليأخذ الإعدادات الجديدة عبر الأمر:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3867_141" style="">
<span class="pln">sudo systemctl restart ufw</span></pre>

<p>
	تصفح بعدها موقع تطبيقك:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_3867_143" style="">
<span class="pln">http</span><span class="pun">:</span><span class="com">//APP_SERVER_1_IP</span></pre>

<p>
	ولاحظ أنه لم يعد متاحًا فقد منعت الاتصالات المباشرة مع خادم التطبيق عبر البوابة 80.
</p>

<p>
	سجل الخروج من الخادم الأول، وكرر الخطوات نفسها على خادم التطبيق الثاني وذلك عبر فتح الملف <code>etc/ufw/after.rules/</code> بصلاحيات sudo:
</p>

<pre class="ipsCode">
sudo nano /etc/ufw/after.rules
</pre>

<p>
	ولصق التعليمات التالية في نهايته:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3867_146" style="">
<span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln">
</span><span class="com"># BEGIN UFW AND DOCKER</span><span class="pln">
</span><span class="pun">*</span><span class="pln">filter
</span><span class="pun">:</span><span class="pln">ufw</span><span class="pun">-</span><span class="pln">user</span><span class="pun">-</span><span class="pln">forward </span><span class="pun">-</span><span class="pln"> </span><span class="pun">[</span><span class="lit">0</span><span class="pun">:</span><span class="lit">0</span><span class="pun">]</span><span class="pln">
</span><span class="pun">:</span><span class="pln">DOCKER</span><span class="pun">-</span><span class="pln">USER </span><span class="pun">-</span><span class="pln"> </span><span class="pun">[</span><span class="lit">0</span><span class="pun">:</span><span class="lit">0</span><span class="pun">]</span><span class="pln">
</span><span class="pun">-</span><span class="pln">A DOCKER</span><span class="pun">-</span><span class="pln">USER </span><span class="pun">-</span><span class="pln">j RETURN </span><span class="pun">-</span><span class="pln">s </span><span class="lit">10.0</span><span class="pun">.</span><span class="lit">0.0</span><span class="pun">/</span><span class="lit">8</span><span class="pln">
</span><span class="pun">-</span><span class="pln">A DOCKER</span><span class="pun">-</span><span class="pln">USER </span><span class="pun">-</span><span class="pln">j RETURN </span><span class="pun">-</span><span class="pln">s </span><span class="lit">172.16</span><span class="pun">.</span><span class="lit">0.0</span><span class="pun">/</span><span class="lit">12</span><span class="pln">
</span><span class="pun">-</span><span class="pln">A DOCKER</span><span class="pun">-</span><span class="pln">USER </span><span class="pun">-</span><span class="pln">j RETURN </span><span class="pun">-</span><span class="pln">s </span><span class="lit">192.168</span><span class="pun">.</span><span class="lit">0.0</span><span class="pun">/</span><span class="lit">16</span><span class="pln">

</span><span class="pun">-</span><span class="pln">A DOCKER</span><span class="pun">-</span><span class="pln">USER </span><span class="pun">-</span><span class="pln">p udp </span><span class="pun">-</span><span class="pln">m udp </span><span class="pun">--</span><span class="pln">sport </span><span class="lit">53</span><span class="pln"> </span><span class="pun">--</span><span class="pln">dport </span><span class="lit">1024</span><span class="pun">:</span><span class="lit">65535</span><span class="pln"> </span><span class="pun">-</span><span class="pln">j RETURN

</span><span class="pun">-</span><span class="pln">A DOCKER</span><span class="pun">-</span><span class="pln">USER </span><span class="pun">-</span><span class="pln">j ufw</span><span class="pun">-</span><span class="pln">user</span><span class="pun">-</span><span class="pln">forward

</span><span class="pun">-</span><span class="pln">A DOCKER</span><span class="pun">-</span><span class="pln">USER </span><span class="pun">-</span><span class="pln">j DROP </span><span class="pun">-</span><span class="pln">p tcp </span><span class="pun">-</span><span class="pln">m tcp </span><span class="pun">--</span><span class="pln">tcp</span><span class="pun">-</span><span class="pln">flags FIN</span><span class="pun">,</span><span class="pln">SYN</span><span class="pun">,</span><span class="pln">RST</span><span class="pun">,</span><span class="pln">ACK SYN </span><span class="pun">-</span><span class="pln">d </span><span class="lit">192.168</span><span class="pun">.</span><span class="lit">0.0</span><span class="pun">/</span><span class="lit">16</span><span class="pln">
</span><span class="pun">-</span><span class="pln">A DOCKER</span><span class="pun">-</span><span class="pln">USER </span><span class="pun">-</span><span class="pln">j DROP </span><span class="pun">-</span><span class="pln">p tcp </span><span class="pun">-</span><span class="pln">m tcp </span><span class="pun">--</span><span class="pln">tcp</span><span class="pun">-</span><span class="pln">flags FIN</span><span class="pun">,</span><span class="pln">SYN</span><span class="pun">,</span><span class="pln">RST</span><span class="pun">,</span><span class="pln">ACK SYN </span><span class="pun">-</span><span class="pln">d </span><span class="lit">10.0</span><span class="pun">.</span><span class="lit">0.0</span><span class="pun">/</span><span class="lit">8</span><span class="pln">
</span><span class="pun">-</span><span class="pln">A DOCKER</span><span class="pun">-</span><span class="pln">USER </span><span class="pun">-</span><span class="pln">j DROP </span><span class="pun">-</span><span class="pln">p tcp </span><span class="pun">-</span><span class="pln">m tcp </span><span class="pun">--</span><span class="pln">tcp</span><span class="pun">-</span><span class="pln">flags FIN</span><span class="pun">,</span><span class="pln">SYN</span><span class="pun">,</span><span class="pln">RST</span><span class="pun">,</span><span class="pln">ACK SYN </span><span class="pun">-</span><span class="pln">d </span><span class="lit">172.16</span><span class="pun">.</span><span class="lit">0.0</span><span class="pun">/</span><span class="lit">12</span><span class="pln">
</span><span class="pun">-</span><span class="pln">A DOCKER</span><span class="pun">-</span><span class="pln">USER </span><span class="pun">-</span><span class="pln">j DROP </span><span class="pun">-</span><span class="pln">p udp </span><span class="pun">-</span><span class="pln">m udp </span><span class="pun">--</span><span class="pln">dport </span><span class="lit">0</span><span class="pun">:</span><span class="lit">32767</span><span class="pln"> </span><span class="pun">-</span><span class="pln">d </span><span class="lit">192.168</span><span class="pun">.</span><span class="lit">0.0</span><span class="pun">/</span><span class="lit">16</span><span class="pln">
</span><span class="pun">-</span><span class="pln">A DOCKER</span><span class="pun">-</span><span class="pln">USER </span><span class="pun">-</span><span class="pln">j DROP </span><span class="pun">-</span><span class="pln">p udp </span><span class="pun">-</span><span class="pln">m udp </span><span class="pun">--</span><span class="pln">dport </span><span class="lit">0</span><span class="pun">:</span><span class="lit">32767</span><span class="pln"> </span><span class="pun">-</span><span class="pln">d </span><span class="lit">10.0</span><span class="pun">.</span><span class="lit">0.0</span><span class="pun">/</span><span class="lit">8</span><span class="pln">
</span><span class="pun">-</span><span class="pln">A DOCKER</span><span class="pun">-</span><span class="pln">USER </span><span class="pun">-</span><span class="pln">j DROP </span><span class="pun">-</span><span class="pln">p udp </span><span class="pun">-</span><span class="pln">m udp </span><span class="pun">--</span><span class="pln">dport </span><span class="lit">0</span><span class="pun">:</span><span class="lit">32767</span><span class="pln"> </span><span class="pun">-</span><span class="pln">d </span><span class="lit">172.16</span><span class="pun">.</span><span class="lit">0.0</span><span class="pun">/</span><span class="lit">12</span><span class="pln">

</span><span class="pun">-</span><span class="pln">A DOCKER</span><span class="pun">-</span><span class="pln">USER </span><span class="pun">-</span><span class="pln">j RETURN
COMMIT
</span><span class="com"># END UFW AND DOCKER</span></pre>

<p>
	أغلق الملف بعد حفظ التغييرات، ومن ثم أعد تشغيل الجدار الناري:
</p>

<pre class="ipsCode">
sudo systemctl restart ufw
</pre>

<p>
	وتصفح موقع الخادم ولاحظ أنه لم يعد متاحًا فقد منعت الاتصالات المباشرة مع هذا الخادم أيضًا.:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_3867_149" style="">
<span class="pln">http</span><span class="pun">:</span><span class="com">//APP_SERVER_2_IP</span></pre>

<p>
	افتح الآن الرابط التالي للتأكد من استمرار قدرة الخادم الوكيل على الوصول إلى التطبيق:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_3867_151" style="">
<span class="pln">https</span><span class="pun">:</span><span class="com">//your_domain_here/polls</span></pre>

<p>
	ولاحظ أن الوصول ممكن وواجهة التطبيق ستظهر أمامك في المتصفح.
</p>

<h2>
	الخاتمة
</h2>

<p>
	قدمنا لك هذا المقال نموذجًا لإعداد تطبيق جانغو في بيئة حاويات موزعة ومرنة قابلة للتوسيع بعدة طرق -في حال دعت الحاجة وازداد الطلب على التطبيق- منها توسيع خوادم تطبيق جانغو أو الخادم الوكيل أو قاعدة البيانات.
</p>

<p>
	ونقترح عليك بعض الأفكار لتطوير إدارة هذه البيئة منها استخدام أداة التمهيد <a href="https://academy.hsoub.com/devops/linux/%D8%A5%D8%AF%D8%A7%D8%B1%D8%A9-%D8%A7%D9%84%D8%AE%D8%AF%D9%85%D8%A7%D8%AA-%D8%B9%D9%84%D9%89-%D9%84%D9%8A%D9%86%D9%83%D8%B3-%D8%A8%D8%A3%D8%AF%D9%88%D8%A7%D8%AA-%D9%86%D8%B8%D8%A7%D9%85-%D8%A7%D9%84%D8%AA%D9%85%D9%87%D9%8A%D8%AF-systemd-r284/" rel="">systemd</a> لإدارة نظامك بشكلٍ أفضل والاطلاع على توثيق دوكر الخاص <a href="https://docs.docker.com/config/containers/start-containers-automatically/" rel="external nofollow">بالتشغيل التلقائي للحاويات</a> لخيارات متخصصة في التشغيل التلقائي للحاويات، والاستعانة بإحدى أدوات تكوين الحاويات مثل <a href="hthttps://academy.hsoub.com/devops/deployment/ansible/" rel="">Ansible</a> أو <a href="https://academy.hsoub.com/devops/deployment/chef/" rel="">Chef</a> لتسهيل العمل في بعض البيئات التي يستخدم فيها أكثر من مضيف الحاوية نفسها.
</p>

<p>
	دون أن ننسى إمكانية استخدام سجل الصور من <a href="https://hub.docker.com/" rel="external nofollow">Docker Hub</a> وكذلك خط أنابيب pipeline لتسهيل نشر واختبار الصور على خوادم متعددة عوضًا عن إعادة بنائها وضبط إعداداتها مجددًا على كل خادم.
</p>

<p>
	ترجمة -وبتصرف- للمقال <a href="https://www.digitalocean.com/community/tutorials/how-to-scale-and-secure-a-django-application-with-docker-nginx-and-let-s-encrypt#conclusion" rel="external nofollow">How To Scale and Secure a Django Application with Docker, Nginx, and Let's Encrypt</a> لصاحبه Hanif Jetha.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		<a href="https://academy.hsoub.com/devops/security/firewalls/%D9%83%D9%8A%D9%81-%D8%AA%D8%B6%D8%A8%D8%B7-%D8%AC%D8%AF%D8%A7%D8%B1-iptables-%D9%84%D8%AD%D9%85%D8%A7%D9%8A%D8%A9-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D9%86%D9%82%D9%88%D9%84%D8%A9-%D8%A8%D9%8A%D9%86-%D8%AE%D9%88%D8%A7%D8%AF%D9%8A%D9%85%D9%83-r118/" rel="">كيف تضبط جدار IPTables لحماية البيانات المنقولة بين خواديمك</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/networking/%D8%A7%D9%84%D9%87%D8%AC%D9%85%D8%A7%D8%AA-%D8%A7%D9%84%D8%A3%D9%85%D9%86%D9%8A%D8%A9-security-attacks-%D9%81%D9%8A-%D8%A7%D9%84%D8%B4%D8%A8%D9%83%D8%A7%D8%AA-%D8%A7%D9%84%D8%AD%D8%A7%D8%B3%D9%88%D8%A8%D9%8A%D8%A9-r540/" rel="">الهجمات الأمنية Security Attacks في الشبكات الحاسوبية</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/general/%D8%B1%D9%85%D9%88%D8%B2-%D8%A7%D9%84%D8%A5%D8%AC%D8%A7%D8%A8%D8%A9-%D9%81%D9%8A-http-r75/" rel="">رموز الإجابة في HTTP</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/servers/web/nginx/%D9%83%D9%8A%D9%81-%D8%AA%D8%A4%D9%85%D9%91%D9%86-%D8%AE%D8%A7%D8%AF%D9%85-%D9%88%D9%8A%D8%A8-nginx-%D8%B9%D9%84%D9%89-%D8%A3%D9%88%D8%A8%D9%86%D8%AA%D9%88-1604-r365/" rel="">كيف تؤمّن خادم ويب NGINX</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">659</guid><pubDate>Sun, 06 Nov 2022 06:44:15 +0000</pubDate></item><item><title>&#x628;&#x646;&#x627;&#x621; &#x62A;&#x637;&#x628;&#x64A;&#x642; &#x62C;&#x627;&#x646;&#x63A;&#x648; &#x628;&#x62E;&#x627;&#x62F;&#x645; Gunicorn &#x648;&#x648;&#x636;&#x639;&#x647; &#x636;&#x645;&#x646; &#x62D;&#x627;&#x648;&#x64A;&#x629; &#x62F;&#x648;&#x643;&#x631;</title><link>https://academy.hsoub.com/devops/cloud-computing/docker/%D8%A8%D9%86%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A8%D8%AE%D8%A7%D8%AF%D9%85-gunicorn-%D9%88%D9%88%D8%B6%D8%B9%D9%87-%D8%B6%D9%85%D9%86-%D8%AD%D8%A7%D9%88%D9%8A%D8%A9-%D8%AF%D9%88%D9%83%D8%B1-r658/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_11/63674ea1bc70a_----Gunicorn----.jpg.787ff1c38c993e4ee05427d866ee5cb7.jpg" /></p>

<p>
	يُعّد <a href="https://academy.hsoub.com/programming/python/django/" rel="">جانغو Django</a> واحدًا من أقوى أطر العمل البرمجية التي تسهل عملية تطوير تطبيقات الويب المبنية باستعمال <a href="https://wiki.hsoub.com/Python" rel="external">بايثون Python</a>، وذلك لما يتمتع به من مميزاتٍ مفيدة مثل استخدامه تقنية ORM أي <a href="https://academy.hsoub.com/programming/python/django/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%86%D9%85%D8%A7%D8%B0%D8%AC-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-django-models-%D9%88%D8%B1%D8%A8%D8%B7%D9%87%D8%A7-%D8%A8%D9%82%D8%A7%D8%B9%D8%AF%D8%A9-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-r1627/" rel="">ربط الكائنات العلاقية object-relational mapper</a> وتوفيره آلية للتحقق من هوية المستخدمين وواجهة مرنة قابلة للتخصيص لإدارة تطبيقك، بالإضافة إلى امتلاكه خاصية <a href="https://docs.djangoproject.com/en/2.1/topics/cache/" rel="external nofollow">التخزين المؤقت</a> وتشجيعه على تصميم الكود النظيف من خلال مصمم عناوين <a href="https://docs.djangoproject.com/en/2.1/topics/http/urls/" rel="external nofollow">URL Dispatcher</a> ونظام القوالب <a href="https://docs.djangoproject.com/en/2.1/topics/templates/" rel="external nofollow">Template system</a>.
</p>

<p>
	سنعرض في هذا المقال التعليمي كيفية بناء تطبيق جانغو اسمه <a href="https://docs.djangoproject.com/en/2.2/intro/tutorial01/" rel="external nofollow">Polls</a> قابل للتوسع والنقل بحاويات <a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%AF%D9%88%D9%83%D8%B1-docker-r607/" rel="">دوكر Docker</a>، وما يلزم من تعديلات ليعمل بفعالية داخل الحاوية ويتكيف مع متغيرات بيئة التشغيل، كما سنتطرق إلى ضبط تسجيل أحداث التطبيق، وتخزين أو تفريغ أصوله الساكنة static assets وهي صفحات جافاسكريبت وملفات التنسيق CSS في مساحة التخزين الكائني object storage لما له من أثر إيجابي على إدارتها إدارة مركزية سلسة ومنظمة بالأخص في بيئة متعددة الحاويات.
</p>

<p>
	وسننشئ بعد تنفيذ التعديلات المطلوبة -المستوحاة من منهجية <a href="https://12factor.net/" rel="external nofollow">Twelve-Factor</a> الخاصة بتطوير تطبيقات ويب سحابية قابلة للتوسع والعمل ضمن الحاويات- على عينة من جانغو <a href="https://github.com/do-community/django-polls" rel="external nofollow">Polls</a> صورة التطبيق image ونشغلّها ضمن حاوية دوكر.
</p>

<p>
	بالنتيجة ستغدو في نهاية هذا المقال قادرًا على وضع تطبيق جانغو في حاوية قابلة للنقل، وفي مقالات لاحقة ستتعلم استخدام <a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-docker-compose-%D8%B9%D9%84%D9%89-%D8%AF%D8%A8%D9%8A%D8%A7%D9%86-r464/" rel="">Docker Compose</a> مع جانغو و<a href="https://academy.hsoub.com/devops/servers/web/nginx/%D9%83%D9%8A%D9%81%D9%8A%D9%91%D8%A9-%D8%B6%D8%A8%D8%B7-nginx-%D9%84%D9%84%D8%B9%D9%85%D9%84-%D9%83%D8%AE%D8%A7%D8%AF%D9%85-%D9%88%D9%8A%D8%A8-%D9%88%D9%83%D9%88%D8%B3%D9%8A%D8%B7-%D8%B9%D9%83%D8%B3%D9%8A-%D9%84%D9%80-apache-%D8%B9%D9%84%D9%89-%D8%AE%D8%A7%D8%AF%D9%85-ubuntu-1804-r396/" rel="">خادم Nginx كوكيل عكسي</a>، ومن ثم تطبيق هذه البنى التي تعرفت عليها في حاويات عنقودية مثل <a href="https://academy.hsoub.com/devops/cloud-computing/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-kubernetes-r467/" rel="">كوبيرنتس Kubernetes</a>.
</p>

<p>
	أخيرًا ننصحك بمتابعة خطوات العمل الواردة هنا بالتسلسل لضمان فهم كافة التغييرات التي سنُجريها على التطبيق، ولكن في حال رغبت بتخطي ذلك فيمكنك الحصول على كود التطبيق المعدل من القسم الخاص بتطبيقات <a href="https://github.com/do-community/django-polls/tree/polls-docker" rel="external nofollow">polls-docker</a> من مستودع GitHub.
</p>

<p>
	هذا المقال جزء من سلسلة قصيرة حول احتواء تطبيقات جانغو بحاويات وإدارتها وإليك فهرس السلسلة:
</p>

<ul>
<li>
		بناء تطبيق جانغو بخادم Gunicorn ووضعه ضمن حاوية دوكر
	</li>
	<li>
		توسيع تطبيق جانغو وتأمينه عبر حاوية دوكر وخادم Nginx وخدمة Let's Encrypt
	</li>
	<li>
		نشر تطبيق جانغو آمن وقابل للتوسيع باستخدام كوبيرنتس
	</li>
</ul>
<h2>
	متطلبات بيئة العمل
</h2>

<p>
	ستحتاج المتطلبات التالية لتتمكن من التطبيق العملي لهذا المقال:
</p>

<ol>
<li>
		تهيئة خادم بنظام تشغيل أوبونتو (استعملنا في المقال إصدار 18.04)، يمكنك الاستعانة بالمقال <a href="https://academy.hsoub.com/devops/linux/%D8%A7%D9%84%D8%AA%D9%87%D9%8A%D8%A6%D8%A9-%D8%A7%D9%84%D8%A3%D9%88%D9%84%D9%8A%D8%A9-%D9%84%D8%AE%D8%A7%D8%AF%D9%85-%D8%A3%D9%88%D8%A8%D9%88%D9%86%D8%AA%D9%88-1804-r431/" rel="">التهيئة الأولية لخادم أوبونتو 18.04</a> لتهيئته.
	</li>
	<li>
		تثبيت دوكر على الخادم، استعن بمقال <a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D8%AF%D9%88%D9%83%D8%B1-%D9%88%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85%D9%87-%D8%B9%D9%84%D9%89-%D8%AF%D8%A8%D9%8A%D8%A7%D9%86-r465/" rel="">كيفية تثبيت دوكر واستخدامه على دبيان</a> فآلية العمل متشابهة في النظامين، دون أن تنسى إضافة مستخدم أوبونتو الخاص بك إلى مجموعة العمل دوكر ليأخذ صلاحيات العمل اللازمة.
	</li>
	<li>
		توفير خدمة تخزين كائني متوافقة مع S3 تؤمن المساحة التخزينية اللازمة لملفات وأصول تطبيق جانغو الساكنة بالإضافة إلى مجموعة من مفاتيح الوصول لإدارة هذه المساحة، وقد استُخدمت خدمة DigitalOcean Space من ديجيتال أوشن DigitalOcean في هذا المقال ويمكنك اختيار خدمة التخزين من المزود الأنسب لك مع إجراء تغييرات بسيطة تلائمها على <a href="https://docs.digitalocean.com/products/spaces/how-to/create/" rel="external nofollow">خطوات الإعداد</a>.
	</li>
	<li>
		نظام إدارة قواعد بيانات <a href="https://docs.djangoproject.com/en/2.2/ref/databases/" rel="external nofollow">متوافق مع جانغو</a> لإنشاء قاعدة بيانات التطبيق، وقد اخترنا DigitalOcean Managed PostgreSQL cluster استرشد <a href="https://www.digitalocean.com/docs/databases/how-to/clusters/create/" rel="external nofollow">بخطواتنا</a> مع تغيير ما يلزم لقاعدة البيانات الخاصة بك.
	</li>
</ol>
<h2>
	الخطوة 1: إنشاء قاعدة البيانات PostgreSQL مع مستخدم قاعدة البيانات
</h2>

<p>
	يتلخص ما سنقوم به في هذه الخطوة بثلاث نقاط هي الاتصال بخادم قواعد بيانات PostgreSQL من خادم أوبونتو، وإنشاء قاعدة بيانات ومستخدم ضمنها خاص بتطبيق جانغو، ومن ثم ضبط إعداداتها لتعمل بكفاءة مع التطبيق، ولتحقيق ذلك سنبدأ بتثبيت عميل <a href="https://academy.hsoub.com/files/18-%D8%A7%D9%84%D8%AF%D9%84%D9%8A%D9%84-%D8%A7%D9%84%D8%B9%D9%85%D9%84%D9%8A-%D8%A5%D9%84%D9%89-%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-postgresql/" rel="">قاعدة بيانات PostgreSQL</a> من مستودع برامج أوبونتو ولكن بعد تحديث دليل مدير الحزم <code>apt</code>، وفق الأمرين:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_5785_6" style="">
<span class="pln">sudo apt update
sudo apt install postgresql</span><span class="pun">-</span><span class="pln">client</span></pre>

<p>
	ثم نؤكد العملية بالضغط على الحرف Y وزر الإدخال Enter، وبعد إتمام التحميل والتثبيت ننتقل للمرحلة التالية وهي إنشاء قاعدة البيانات والمستخدم الخاص بتطبيق جانغو.
</p>

<p>
	تتطلب هذه العملية معرفة محددات الاتصال مع PostgreSQL cluster مثل عنوان المضيف واسم قاعدة البيانات ورقم البوابة كذلك اسم المستخدم وكلمة المرور، ويمكنك الحصول على كافة هذه البيانات من لوحة التحكم الخاصة بخدمة PostgreSQL cluster ضمن مربع خاص بمحددات الاتصال إن كنت تستخدم نظام إدارة قواعد بيانات سحابي (مثل DigitalOcean أو غيره).
</p>

<p>
	والآن باستخدام هذه المحددات ونسخة العميل من PostgreSQL التي سبق تثبيتها، يمكنك تسجيل الدخول إلى PostgreSQL cluster وفق الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_5785_8" style="">
<span class="pln">psql </span><span class="pun">-</span><span class="pln">U username </span><span class="pun">-</span><span class="pln">h host </span><span class="pun">-</span><span class="pln">p port </span><span class="pun">-</span><span class="pln">d database </span><span class="pun">--</span><span class="kwd">set</span><span class="pun">=</span><span class="pln">sslmode</span><span class="pun">=</span><span class="kwd">require</span></pre>

<p>
	ستُطلب منك كلمة المرور، أدخلها، واضغط زر الإدخال Enter، لتنتقل إلى موجه أوامر PostgreSQL الذي يخولك التحكم بقاعدة البيانات.
</p>

<p>
	أنشئ قاعدة بيانات خاصة بمشروعك باسم <code>polls</code> وفق التعليمة التالية (مع التنويه إلى أن كافة تعليمات psql يجب أن تنتهي بفاصلة منقوطة):
</p>

<pre class="ipsCode">
CREATE DATABASE polls;
</pre>

<p>
	ثم توجه إلى قاعدة البيانات <code>polls</code> الخاصة بك كما يلي:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_5785_10" style="">
<span class="pln">\c polls</span><span class="pun">;</span></pre>

<p>
	وأنشئ ضمنها مستخدم قاعدة بيانات لمشروعك باسم sammy مثلًا -أو أي اسم- كما يلي واحرص على حمايته بكلمة مرور قوية:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_5785_12" style="">
<span class="pln">CREATE USER sammy WITH PASSWORD </span><span class="str">'password'</span><span class="pun">;</span></pre>

<p>
	اضبط بعد ذلك القيم الافتراضية لمحددات اتصال المستخدم المنشأ بالتوافق مع <a href="https://docs.djangoproject.com/en/2.0/ref/databases/#optimizing-postgresql-s-configuration" rel="external nofollow">توصيات جانغو</a> بحيث لا يتطلب الأمر الاستعلام عنها وإعادة تعيينها مع كل تأسيس جديد للاتصال ما سينعكس إيجابًا على تسريع عمليات قاعدة البيانات، والمحددات المطلوب ضبطها هي: الترميز encoding الذي يأخذ القيمة الافتراضية <code>UTF-8</code>، وآلية عزل العمليات transaction isolation scheme الذي يضبط إلى القيمة "read committed" ومهمته منع قراءة العمليات غير المكتملة uncommitted transactions، وأخيرًا المنطقة الزمنية نثبتها على <code>UTC</code>، وفق التعليمات التالية:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_5785_14" style="">
<span class="pln">ALTER ROLE sammy SET client_encoding TO </span><span class="str">'utf8'</span><span class="pun">;</span><span class="pln">
ALTER ROLE sammy SET default_transaction_isolation TO </span><span class="str">'read committed'</span><span class="pun">;</span><span class="pln">
ALTER ROLE sammy SET timezone TO </span><span class="str">'UTC'</span><span class="pun">;</span></pre>

<p>
	يتبقى الإعداد الأخير وهو منح مستخدم قاعدة البيانات المنشأ صلاحيات الإدارة الكاملة لقاعدة البيانات:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_5785_16" style="">
<span class="pln">GRANT ALL PRIVILEGES ON DATABASE polls TO sammy</span><span class="pun">;</span></pre>

<p>
	وبعدها اخرج من موجه أوامر PostgreSQL باستخدام:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_5785_18" style="">
<span class="pln">\q</span></pre>

<p>
	جهزنا بذلك قاعدة البيانات ليُديرها تطبيق جانغو، لننتقل للخطوة الثانية المتمثلة بنسخ كود التطبيق Polls من GitHub وتحديد حزم اعتماديات بايثون اللازمة له.
</p>

<h2>
	الخطوة 2: استنساخ مستودع التطبيق والتصريح عن اعتماديات عمله
</h2>

<p>
	سنستخدم لعملية الاستنساخ مستودع التطبيق <a href="https://github.com/do-community/django-polls" rel="external nofollow">django-polls</a> الذي يتضمن الكود الكامل لتطبيق Polls المجهز لأغراض <a href="https://docs.djangoproject.com/en/2.1/intro/tutorial01/" rel="external nofollow">البرنامج التعليمي</a> في مشروع جانغو.
</p>

<p>
	بدايةً سجل الدخول إلى خادم أوبونتو وأنشئ مجلدًا يدعى <code>polls-project</code> ومن ثم استخدم الأمر <code>git</code> لاستنساخ مستودع <code>django-polls</code> من GitHub:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5785_20" style="">
<span class="pln">mkdir polls</span><span class="pun">-</span><span class="pln">project
cd polls</span><span class="pun">-</span><span class="pln">project
git clone https</span><span class="pun">://</span><span class="pln">github</span><span class="pun">.</span><span class="pln">com</span><span class="pun">/</span><span class="kwd">do</span><span class="pun">-</span><span class="pln">community</span><span class="pun">/</span><span class="pln">django</span><span class="pun">-</span><span class="pln">polls</span><span class="pun">.</span><span class="pln">git</span></pre>

<p>
	انتقل إلى <code>django-polls</code> واستعرض محتوياته بتعليمة <code>ls</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5785_22" style="">
<span class="pln">cd django</span><span class="pun">-</span><span class="pln">polls
ls</span></pre>

<p>
	وسينتج الخرج التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5785_28" style="">
<span class="pln">LICENSE  README</span><span class="pun">.</span><span class="pln">md  manage</span><span class="pun">.</span><span class="pln">py  mysite  polls  templates</span></pre>

<p>
	الذي يبين لك محتويات تطبيق <code>django-polls</code>، وهي العناصر المبينة أدناه، نعددها لك بإيجاز ويمكنك تعلم المزيد عنها بالاطلاع على التوثيق الخاص <a href="https://docs.djangoproject.com/en/2.1/intro/tutorial01/#creating-a-project" rel="external nofollow">بإنشاء المشاريع</a> من توثيقات جانغو الرسمية:
</p>

<ol>
<li>
		<code>manage.py</code>: أداة سطر الأوامر الرئيسية لتنفيذ أوامر معالجة التطبيق.
	</li>
	<li>
		<code>polls</code>: يتضمن أكواد التطبيق <code>polls</code>.
	</li>
	<li>
		<code>mysite</code>: يتضمن أكواد وإعدادات جانغو <code>project-scope</code>.
	</li>
	<li>
		<code>templates</code>: يتضمن قوالب خاصة بواجهة الإدارة.
	</li>
</ol>
<p>
	أنشئ بعدها ملفًا نصيًا باسم requirements.txt ضمن نفس المسار <code>polls-project/django-polls</code> الذي تكلمنا عنه، وافتحه باستخدام محرر النصوص والصق ضمنه اعتماديات بايثون التالية، ثم احفظ التغييرات على الملف وأغلقه:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5785_30" style="">
<span class="pln">boto3</span><span class="pun">==</span><span class="lit">1.9</span><span class="pun">.</span><span class="lit">252</span><span class="pln">
botocore</span><span class="pun">==</span><span class="lit">1.12</span><span class="pun">.</span><span class="lit">252</span><span class="pln">
</span><span class="typ">Django</span><span class="pun">==</span><span class="lit">2.2</span><span class="pun">.</span><span class="lit">6</span><span class="pln">
django</span><span class="pun">-</span><span class="pln">storages</span><span class="pun">==</span><span class="lit">1.7</span><span class="pun">.</span><span class="lit">2</span><span class="pln">
docutils</span><span class="pun">==</span><span class="lit">0.15</span><span class="pun">.</span><span class="lit">2</span><span class="pln">
gunicorn</span><span class="pun">==</span><span class="lit">19.9</span><span class="pun">.</span><span class="lit">0</span><span class="pln">
jmespath</span><span class="pun">==</span><span class="lit">0.9</span><span class="pun">.</span><span class="lit">4</span><span class="pln">
psycopg2</span><span class="pun">==</span><span class="lit">2.8</span><span class="pun">.</span><span class="lit">3</span><span class="pln">
python</span><span class="pun">-</span><span class="pln">dateutil</span><span class="pun">==</span><span class="lit">2.8</span><span class="pun">.</span><span class="lit">0</span><span class="pln">
pytz</span><span class="pun">==</span><span class="lit">2019.3</span><span class="pln">
s3transfer</span><span class="pun">==</span><span class="lit">0.2</span><span class="pun">.</span><span class="lit">1</span><span class="pln">
six</span><span class="pun">==</span><span class="lit">1.12</span><span class="pun">.</span><span class="lit">0</span><span class="pln">
sqlparse</span><span class="pun">==</span><span class="lit">0.3</span><span class="pun">.</span><span class="lit">0</span><span class="pln">
urllib3</span><span class="pun">==</span><span class="lit">1.25</span><span class="pun">.</span><span class="lit">6</span></pre>

<p>
	كما تلاحظ فقد أوردنا بدقة كافة اعتماديات بايثون التي يحتاج إليها المشروع ومن بينها إضافة <code>django-storages</code> الخاصة بتخزين ملفات وأصول الموقع الساكنة على مساحة التخزين الكائني، وإضافة <code>gunicorn</code> الخاصة بخادم WSGI، كذلك مُحول <code>psycopg2</code> لمواءمة التخاطب مع قاعدة بيانات PostgreSQL.
</p>

<p>
	والآن بعد أن أنهينا استنساخ التطبيق وحددنا ما يلزمه من اعتماديات لنبدأ بتعديله ليصبح قابلًا للنقل.
</p>

<h2>
	الخطوة 3: تكييف جانغو مع متغيرات البيئة
</h2>

<p>
	توصي منهجية <a href="https://12factor.net/config" rel="external nofollow">Twelve-Factor</a> باستخراج بيانات الإعدادات المدمجة مع كود التطبيق التي لا يمكن تعديلها إلاّ بتعديل الكود، وفصلها عن الكود ما يسمح للتطبيق بالتكيّف وتغيير سلوكه تبعًا لمتغيرات بيئة التشغيل، يوافقها في ذلك توصيات إعداد الحاويات لدى كلٍ من دوكر وكوبيرنتس Kubernetes، لذا سنتبع هذا النمط ونعدّل ملف الإعدادات django-polls/mysite/settings.py الخاص بتطبيق جانغو -وهو وحدة بايثون- بحيث يحصل على البيانات من بيئة التشغيل الفعلية المحلية عوضًا عن قراءتها من الكود، وذلك باستخدام الدالة <code>getenv</code> مع الوحدة <code>os</code>. لنبدأ بالتطبيق عمليًا!
</p>

<p>
	سنجعل المحددات المدمجة مع الكود ضمن الملف settings.py تأخذ قيمها من متغيرات البيئة عبر استدعاء الدالة <code>os.getenv</code> وتحديد اسم المتغير المرتبط ضمن قوسين، مع العلم بإمكانية تمرير قيمة ثانية للدالة تسمى بالقيمة الافتراضية أو الاحتياطية تُرجعها الدالة في حال كان المتغير غير معرف، لاحظ التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5785_32" style="">
<span class="pun">…</span><span class="pln">
SECRET_KEY </span><span class="pun">=</span><span class="pln"> os</span><span class="pun">.</span><span class="pln">getenv</span><span class="pun">(</span><span class="str">'DJANGO_SECRET_KEY'</span><span class="pun">)</span><span class="pln">
DEBUG </span><span class="pun">=</span><span class="pln"> os</span><span class="pun">.</span><span class="pln">getenv</span><span class="pun">(</span><span class="str">'DJANGO_DEBUG'</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">False</span><span class="pun">)</span><span class="pln">
ALLOWED_HOSTS </span><span class="pun">=</span><span class="pln"> os</span><span class="pun">.</span><span class="pln">getenv</span><span class="pun">(</span><span class="str">'DJANGO_ALLOWED_HOSTS'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'127.0.0.1'</span><span class="pun">).</span><span class="pln">split</span><span class="pun">(</span><span class="str">','</span><span class="pun">)</span><span class="pln">
</span><span class="pun">…</span></pre>

<p>
	سيبحث جانغو عن متغير بيئة يدعى <code>DJANGO_SECRET_KEY</code> ويسند قيمته إلى المتغير <code>SECRET_KEY</code>، مع العلم أننا لم نمرر له قيمة احتياطية يعتمدها في حال أن المتغير لم يكن معرفًا، ذلك أننا نريده موحدًا على مستوى جميع خوادم التطبيق، وبنفس الوقت لا يجب أن يكون مدمجًا مع الكود، فكان تعريفه خارجيًا دون قيمة احتياطية هو الحل الأنسب، حيث أن فشل تشغيل التطبيق بسبب عدم توفر المتغير يعدّ بالتأكيد خيارًا أفضل من تشغيله مع المشاكل التي يمكن أن تنجم عن استخدام قيم متباينة من المفتاح السري <code>SECRET_KEY</code> في نسخ التطبيق المختلفة.
</p>

<p>
	بنفس الطريقة للمتغير أو المحدد <code>DEBUG</code> سيبحث عن متغير بيئة يدعى <code>DJANGO_DEBUG</code> ويأخذ قيمته، وفي حال لم يكن معرفًا فإنه سيأخذ القيمة الاحتياطية <code>false</code> التي ستحمينا من فقدان أي بيانات قد تكون حساسة ما لم نضبط المتغير إلى قيمة <code>true</code> عمدًا.
</p>

<p>
	أما بالنسبة للمتغير أو المحدد <code>ALLOWED_HOSTS</code> فسيُحضر قيمة متغير البيئة المرتبط به <code>DJANGO_ALLOWED_HOSTS</code> ويأخذ قيمته بعد فصله إلى قائمة بايثون باستخدام الدالة <code>()split</code> مع اعتماد <code>,</code> كفاصل، وفي حال أن المتغير لم يكن معرفًا يأخذ المحدد القيمة <code>127.0.0.1</code> الاحتياطية.
</p>

<p>
	فور انتهائك من تعديل المحددات الثلاثة السابقة انتقل لتعديل محدد قاعدة البيانات <code>DATABASES</code> ضمن نفس الملف بحيث يأخذ أيضًا قيمته الافتراضية <code>default</code> من متغيرات البيئة، وذلك وفق التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5785_34" style="">
<span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> 
</span><span class="com"># Database</span><span class="pln">
</span><span class="com"># https://docs.djangoproject.com/en/2.1/ref/settings/#databases</span><span class="pln">

DATABASES </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
     </span><span class="str">'default'</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
         </span><span class="str">'ENGINE'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'django.db.backends.{}'</span><span class="pun">.</span><span class="pln">format</span><span class="pun">(</span><span class="pln">
             os</span><span class="pun">.</span><span class="pln">getenv</span><span class="pun">(</span><span class="str">'DATABASE_ENGINE'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'sqlite3'</span><span class="pun">)</span><span class="pln">
         </span><span class="pun">),</span><span class="pln">
         </span><span class="str">'NAME'</span><span class="pun">:</span><span class="pln"> os</span><span class="pun">.</span><span class="pln">getenv</span><span class="pun">(</span><span class="str">'DATABASE_NAME'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'polls'</span><span class="pun">),</span><span class="pln">
         </span><span class="str">'USER'</span><span class="pun">:</span><span class="pln"> os</span><span class="pun">.</span><span class="pln">getenv</span><span class="pun">(</span><span class="str">'DATABASE_USERNAME'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'myprojectuser'</span><span class="pun">),</span><span class="pln">
         </span><span class="str">'PASSWORD'</span><span class="pun">:</span><span class="pln"> os</span><span class="pun">.</span><span class="pln">getenv</span><span class="pun">(</span><span class="str">'DATABASE_PASSWORD'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'password'</span><span class="pun">),</span><span class="pln">
         </span><span class="str">'HOST'</span><span class="pun">:</span><span class="pln"> os</span><span class="pun">.</span><span class="pln">getenv</span><span class="pun">(</span><span class="str">'DATABASE_HOST'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'127.0.0.1'</span><span class="pun">),</span><span class="pln">
         </span><span class="str">'PORT'</span><span class="pun">:</span><span class="pln"> os</span><span class="pun">.</span><span class="pln">getenv</span><span class="pun">(</span><span class="str">'DATABASE_PORT'</span><span class="pun">,</span><span class="pln"> </span><span class="lit">5432</span><span class="pun">),</span><span class="pln">
         </span><span class="str">'OPTIONS'</span><span class="pun">:</span><span class="pln"> json</span><span class="pun">.</span><span class="pln">loads</span><span class="pun">(</span><span class="pln">
             os</span><span class="pun">.</span><span class="pln">getenv</span><span class="pun">(</span><span class="str">'DATABASE_OPTIONS'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'{}'</span><span class="pun">)</span><span class="pln">
         </span><span class="pun">),</span><span class="pln">
     </span><span class="pun">}</span><span class="pln">
 </span><span class="pun">}</span><span class="pln">
 </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span></pre>

<p>
	لاحظ أننا استخدمنا <code>json.loads</code> لتعيين قيمة محدد قاعدة البيانات وفق الصيغة:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5785_36" style="">
<span class="pln">DATABASES</span><span class="pun">[</span><span class="str">'default'</span><span class="pun">][</span><span class="str">'OPTIONS'</span><span class="pun">]</span></pre>

<p>
	نستنتج من هذه الحالة أن متغيرات البيئة لن تكون دومًا سلاسل بسيطة سهلة القراءة فيمكن أن تأخذ لأنواعًا مختلفة من البيانات، ومن هنا تبرز أهمية المرونة التي يوفرها تمرير المتغيرات بملف <a href="https://academy.hsoub.com/programming/javascript/%D8%AA%D8%B9%D9%84%D9%85-json-r604/" rel="">JSON</a> حتى ولو كانت على حساب الوضوح في قراءة الإعدادات.
</p>

<p>
	دون أن تنسى تفعيل مكتبة <code>json</code> ضمن ملف الإعدادات <code>settings.py</code> وفق التالي حتى تتمكن من استخدام هذا النوع من المتغيرات:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5785_38" style="">
<span class="str">"""
Django settings for mysite project.

Generated by 'django-admin startproject' using Django 2.1.

For more information on this file, see
https://docs.djangoproject.com/en/2.1/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/2.1/ref/settings/
"""</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> os
</span><span class="kwd">import</span><span class="pln"> json
</span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span></pre>

<p>
	نشير أيضًا إلى محدد آخر يستدعي الاهتمام هو اسم قاعدة البيانات:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_5785_40" style="">
<span class="pln">DATABASES</span><span class="pun">[</span><span class="str">'default'</span><span class="pun">][</span><span class="str">'NAME'</span><span class="pun">]</span></pre>

<p>
	فهو يدل على اسم قاعدة البيانات في معظم <a href="https://academy.hsoub.com/devops/servers/databases/%D9%85%D9%82%D8%A7%D8%B1%D9%86%D8%A9-%D8%A8%D9%8A%D9%86-%D8%A3%D9%86%D8%B8%D9%85%D8%A9-%D8%A5%D8%AF%D8%A7%D8%B1%D8%A9-%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D8%A7%D9%84%D8%B9%D9%84%D8%A7%D9%82%D9%8A%D8%A9-sqlite-%D9%85%D8%B9-mysql-%D9%85%D8%B9-postgresql-r72/" rel="">نظم إدارة قواعد البيانات العلاقية</a>، أما في حال استخدامك قاعدة بيانات SQLite فإنه سيشير إلى ملف قاعدة البيانات لذا انتبه لهذه النقطة.
</p>

<p>
	وفي ختام التعديل لا بد لنا من الإشارة إلى أن الملف setting.py هو في النهاية ملف بايثون وبالتالي يمكنك قراءته والتعديل عليه بالطريقة التي تفضلها وما اتبعناه لهذا الغرض كان إحدى سبل التعديل المتاحة.
</p>

<p>
	ضبطنا حتى الآن إعدادات جانغو الرئيسية ومتغيراته بما فيها محددات قاعدة البيانات لتناسب النمط العام القابل للنقل، وسنتابع العملية في الخطوة التالية ولكن هذه المرة على مكونات وأصول التطبيق الساكنة مثل جافاسكريبت وصفحات CSS لتكون متاحة مركزيًا على خدمة التخزين الكائني المتوافقة مع S3.
</p>

<h2>
	الخطوة 4: تفريغ المكونات الساكنة Offloading Static Assets
</h2>

<p>
	تحتوي بعض بيئات التطوير على عدة حاويات جانغو وقد تكون كثيرة العدد ما يصعب على المطوّر مهمة الاحتفاظ بإصدارات محددة من الملفات والمكونات الساكنة الخاصة بكل حاوية، فيلجأ إلى جعل المشترك منها متوفرًا عبر وحدة تخزين خارجية يتم الوصول إليها من خلال الشبكة عوضًا عن توفيرها محليًا ومحاولة مزامنتها دائمًا باستخدام النسخ الاحتياطي الدوري أو التكرارية أو التحميل الروتيني، تسمى هذه العملية بالتفريغ Offloading.
</p>

<p>
	أما عن ضبط المحددات الخاصة بهذه الوحدة سنترك بعضها مدمجًا مع الكود hard-coded ونمرر بعضها الآخر لحاويات تطبيق جانغو عبر متغيرات البيئة بنفس الطريقة التي اتبعناها في الخطوة /3/ السابقة للتعامل مع محددات قاعدة البيانات.
</p>

<p>
	يتطلب هذا النوع من التخزين تثبيت حزمة <a href="https://github.com/jschneier/django-storages" rel="external nofollow">django-storages</a> التي تدعم وجود واجهات خلفية بعيدة للتطبيق تُخزن عليها المكونات والأصول الساكنة بما فيها وحدات التخزين الكائني المتوافقة مع S3 مثل DigitalOcean Space المستخدمة في مقالنا أو غيرها، ويمكنك الاستعانة بمقالة أجنبية أخرى تشرح عملية <a href="https://www.digitalocean.com/community/tutorials/how-to-set-up-a-scalable-django-app-with-digitalocean-managed-databases-and-spaces#step-7-%E2%80%94-offloading-static-files-to-digitalocean-spaces" rel="external nofollow">التفريغ في الخطوة رقم 7 منها</a>.
</p>

<p>
	سنبدأ بتعديل الملف django-polls/mysite/settings.py وهو نفس الملف الذي تعاملنا معه في الفقرة السابقة بإضافة التطبيق storages على قائمة التطبيقات التي يستخدمها جانغو وذلك من خلال المحدد <code>INSTALLED_APPS</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_5785_42" style="">
<span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln">
INSTALLED_APPS </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
    </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln">
    </span><span class="str">'django.contrib.staticfiles'</span><span class="pun">,</span><span class="pln">
    </span><span class="str">'storages'</span><span class="pun">,</span><span class="pln">
</span><span class="pun">]</span><span class="pln">
</span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span></pre>

<p>
	تذكر أن التطبيق <code>storages</code> هو أحد الاعتماديات التي حددناها في الملف requirements.txt الذي ورد في الخطوة /1/ من الإعداد (وذلك من خلال <code>django-storages</code>).
</p>

<p>
	وبعد <code>INSTALLED_APPS</code> سنعدّل المحدد <code>STATIC_URL</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5785_44" style="">
<span class="pun">#</span><span class="pln"> </span><span class="typ">Static</span><span class="pln"> files </span><span class="pun">(</span><span class="pln">CSS</span><span class="pun">,</span><span class="pln"> </span><span class="typ">JavaScript</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Images</span><span class="pun">)</span><span class="pln">
</span><span class="pun">#</span><span class="pln"> https</span><span class="pun">:</span><span class="com">//docs.djangoproject.com/en/2.1/howto/static-files/</span><span class="pln">

</span><span class="pun">#</span><span class="pln"> </span><span class="typ">Moving</span><span class="pln"> </span><span class="kwd">static</span><span class="pln"> assets to </span><span class="typ">DigitalOcean</span><span class="pln"> </span><span class="typ">Spaces</span><span class="pln"> as per</span><span class="pun">:</span><span class="pln">
</span><span class="pun">#</span><span class="pln"> https</span><span class="pun">:</span><span class="com">//www.digitalocean.com/community/tutorials/how-to-set-up-object-storage-with-django</span><span class="pln">
AWS_ACCESS_KEY_ID </span><span class="pun">=</span><span class="pln"> os</span><span class="pun">.</span><span class="pln">getenv</span><span class="pun">(</span><span class="str">'STATIC_ACCESS_KEY_ID'</span><span class="pun">)</span><span class="pln">
AWS_SECRET_ACCESS_KEY </span><span class="pun">=</span><span class="pln"> os</span><span class="pun">.</span><span class="pln">getenv</span><span class="pun">(</span><span class="str">'STATIC_SECRET_KEY'</span><span class="pun">)</span><span class="pln">

AWS_STORAGE_BUCKET_NAME</span><span class="pun">=</span><span class="pln"> os</span><span class="pun">.</span><span class="pln">getenv</span><span class="pun">(</span><span class="str">'STATIC_BUCKET_NAME'</span><span class="pun">)</span><span class="pln">
AWS_S3_ENDPOINT_URL </span><span class="pun">=</span><span class="pln"> os</span><span class="pun">.</span><span class="pln">getenv</span><span class="pun">(</span><span class="str">'STATIC_ENDPOINT_URL'</span><span class="pun">)</span><span class="pln">
AWS_S3_OBJECT_PARAMETERS </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="str">'CacheControl'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'max-age=86400'</span><span class="pun">,</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
AWS_LOCATION </span><span class="pun">=</span><span class="pln"> </span><span class="str">'static'</span><span class="pln">
AWS_DEFAULT_ACL </span><span class="pun">=</span><span class="pln"> </span><span class="str">'public-read'</span><span class="pln">

STATICFILES_STORAGE </span><span class="pun">=</span><span class="pln"> </span><span class="str">'storages.backends.s3boto3.S3Boto3Storage'</span><span class="pln">

STATIC_URL</span><span class="pun">=</span><span class="str">'{}/{}/'</span><span class="pun">.</span><span class="pln">format</span><span class="pun">(</span><span class="pln">AWS_S3_ENDPOINT_URL</span><span class="pun">,</span><span class="pln"> AWS_LOCATION</span><span class="pun">)</span><span class="pln">
STATIC_ROOT </span><span class="pun">=</span><span class="pln"> </span><span class="str">'static/'</span></pre>

<p>
	كما أسلفنا في مقدمة الفقرة، فإن بعض المحددات ستبقى مدمجة مع الكود hard-coded وهي:
</p>

<ol>
<li>
		<code>STATICFILES_STORAGE</code>: يحدد الواجهة الخلفية التي يستخدمها جانغو لتفريغ الملفات الساكنة والقيمة <code>S3Boto3Storage</code> لهذا المحدد ستعمل مع كافة خدمات التخزين المتوافقة مع S3 بما فيها DigitalOcean Spaces.
	</li>
	<li>
		<code>AWS_S3_OBJECT_PARAMETERS</code>: يحدد معايير التخزين المؤقت cache control الخاصة بمكونات التطبيق الساكنة.
	</li>
	<li>
		<code>AWS_LOCATION</code>: يحدد مسار تخزين كامل الملفات الساكنة على خدمة التخزين الكائني وهذا المسار يسمى <code>static</code>.
	</li>
	<li>
		<code>AWS_DEFAULT_ACL</code>: يحدد قائمة التحكم بالوصول ACL لملفات التطبيق الساكنة، واختيار القيمة <code>public-read</code> لهذا المحدد يعني أن الملفات قابلة للقراءة من قبل العامة.
	</li>
	<li>
		<code>STATIC_URL</code>: يحدد العنوان الرئيس للملفات الساكنة Base URL ويتشكل بجمع مكونين الأول هو endpoint URL ويشير إلى خدمة التخزين الكائني والثاني هو مسار وجود الملفات الساكنة للتطبيق.
	</li>
	<li>
		<code>STATIC_ROOT</code>: يحدد المسار المحلي لتجميع الساكنة قبل نسخها إلى خدمة التخزين الكائني.
	</li>
</ol>
<p>
	أما المحددات التي ستعطي المرونة للاتصال مع وحدة التخزين وتأخذ قيمها من متغيرات البيئة فهي:
</p>

<ol>
<li>
		<code>AWS_ACCESS_KEY_ID</code>: يأخذ قيمته من متغير البيئة <code>STATIC_ACCESS_KEY_ID</code> الذي يشير إلى معرف مفتاح الوصول الخاص بخدمة التخزين (وهي DigitalOcean Spaces في حالتنا).
	</li>
	<li>
		<code>AWS_SECRET_ACCESS_KEY</code>: يأخذ قيمته من المتغير <code>STATIC_SECRET_KEY</code> ويشير إلى المفتاح السري لخدمة التخزين.
	</li>
	<li>
		<code>AWS_STORAGE_BUCKET_NAME</code>: يأخذ قيمته من المتغير <code>STATIC_BUCKET_NAME</code> ويدل على الموقع الذي ستُرفع ملفات جانغو الساكنة إليه على خدمة التخزين.
	</li>
	<li>
		<code>AWS_S3_ENDPOINT_URL</code>: يأخذ قيمته من المحدد <code>STATIC_ENDPOINT_URL</code> ويستخدم للوصول إلى خدمة التخزين الكائني، فسيكون شكله من قبيل هذا العنوان <code><a href="https://nyc3.digitaloceanspaces.com" ipsnoembed="false" rel="external nofollow">https://nyc3.digitaloceanspaces.com</a></code> في خدمة DigitalOcean Spaces على سبيل المثال بالطبع مع بعض الاختلافات المتعلقة بالمنطقة التي تحوي ملفاتك.
	</li>
</ol>
<p>
	لا تنسَ حفظ التغيرات على الملف <code>settings.py</code> قبل إغلاقه.
</p>

<p>
	ننهي بذلك الضبط المتعلق بالمكونات الساكنة، وبموجبه سيرفع جانغو ملفاته إلى وحدة التخزين الكائني البعيدة في كل مرة تشغل فيها <code>manager.py collectionstatic</code> لتُجمَّع الملفات الساكنة لمشروعك.
</p>

<p>
	ويتبقى لنا الإشارة لنقطة اختيارية وليست ملزمة لتسريع تسليم موقعك وهي الاشتراك بخدمة <a href="https://academy.hsoub.com/devops/cloud-computing/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%B4%D8%A8%D9%83%D8%A7%D8%AA-%D8%AA%D8%B3%D9%84%D9%8A%D9%85-%D8%A7%D9%84%D9%85%D8%AD%D8%AA%D9%88%D9%89-cdn-%D9%84%D8%AA%D8%B3%D8%B1%D9%8A%D8%B9-%D8%AA%D8%B3%D9%84%D9%8A%D9%85-%D8%A7%D9%84%D9%85%D8%AD%D8%AA%D9%88%D9%89-%D8%A7%D9%84%D8%AB%D8%A7%D8%A8%D8%AA-r393/" rel="">شبكة توصيل المحتوى CDN</a> التي تحتفظ بنسخ من ملفات الموقع على خوادم الشبكة وتقوم بتسليم المستخدم الصفحات المطلوبة من أقرب نقطة جغرافية لمكانه، ويمكنك الحصول عليها من مزود خدمة التخزين الكائني نفسه إن كان يوفرها (على غرار ما يفعل DigitalOcean Spaces) أو من مصدر آخر، يمكنك كذلك إعداد نطاق فرعي للمساحة التخزينية المخصصة لك وهو إجراء اختياري أيضًا، للاستزادة استعن بالمقال الخاص بخدمة <a href="https://academy.hsoub.com/devops/cloud-computing/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%B4%D8%A8%D9%83%D8%A7%D8%AA-%D8%AA%D8%B3%D9%84%D9%8A%D9%85-%D8%A7%D9%84%D9%85%D8%AD%D8%AA%D9%88%D9%89-cdn-%D9%84%D8%AA%D8%B3%D8%B1%D9%8A%D8%B9-%D8%AA%D8%B3%D9%84%D9%8A%D9%85-%D8%A7%D9%84%D9%85%D8%AD%D8%AA%D9%88%D9%89-%D8%A7%D9%84%D8%AB%D8%A7%D8%A8%D8%AA-r393/" rel="">CDN</a>.
</p>

<p>
	لننتقل الآن للخطوة التالية وهي ضبط تسجيل أحداث جانغو ضمن مجرى قياسي للخرج والخطأ STDOUT و STDERR تتعامل معه <a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D9%85%D8%A7-%D9%87%D9%8A-%D8%B5%D9%88%D8%B1%D8%A9-%D8%A7%D9%84%D8%AD%D8%A7%D9%88%D9%8A%D8%A9-container-image%D8%9F-r587/" rel="">حاوية دوكر</a> من خلال docker logs، وهذا سيكون تعديلنا الأخير على settings.py.
</p>

<h2>
	الخطوة 5: ضبط تسجيل الأحداث
</h2>

<p>
	إذا كنت تعتمد خادم التطوير المدمج في إطار العمل جانغو ليؤدي دور <a href="https://academy.hsoub.com/programming/general/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-http-%D8%B4%D8%B1%D8%AD-%D8%A7%D9%84%D8%AA%D8%AE%D8%A7%D8%B7%D8%A8-%D8%A8%D9%8A%D9%86-%D8%A7%D9%84%D8%B9%D9%85%D9%8A%D9%84-%D9%88%D8%A7%D9%84%D8%AE%D8%A7%D8%AF%D9%85-r74/" rel="">خادم HTTP</a> لتطبيقك، وفعلت خيار التنقيح بإعطاء <code>DEBUG</code> القيمة <code>true</code> فإن جانغو سيُسجل الأحداث تلقائيًا في مجرى خرج قياسي STDOUT ومجرى خطأ قياسي STDERR، أما الإخلال بأي من الشرطين سواءً باستخدام خادم HTTP مغاير لخادم التطوير أو بإعطاء القيمة <code>false</code> للمحدد <code>DEBUG</code>، وكلاهما شائع في بيئة العمل الفعلية، فستتغير آلية التسجيل للتطبيق فبدلًا من تسجيل كافة الأحداث التي تحمل مستوى الخطورة <code>INFO</code> وما فوق ضمن المجرى القياسي، فإنه سيبعث الأحداث التي تحمل المستوى <code>ERROR</code> أو <code>CRITICAL</code> إلى البريد الإلكتروني لمدير النظام.
</p>

<p>
	هذا السلوك من جانغو لا يعدّ ملائمًا لبيئة الحاويات بالأخص المتعددة مثل <a href="https://academy.hsoub.com/devops/cloud-computing/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-kubernetes-r467/" rel="">Kubernetes</a> (التي تعتمد النظام العنقودي) ففي هذه البيئات تُجمع تسجيلات الأحداث ضمن ملفات قياسية لكلٍ من الخرج والخطأ وتخزن في مسار مركزي على نظام ملفات العقدة بحيث تكون متاحة لأوامر الحاوية مثل <code>kubectl</code> و <code>docker</code>، وسهلة المراقبة والتتبع على فرق التشغيل.
</p>

<p>
	من هذه النقطة تبرز الحاجة لتعديل تسجيل أحداث جانغو ليتماشى مع هذه البنية ويعطي ملفات الخرج المرغوبة، والتطبيق مرن من هذه الناحية، فهو يستخدم وحدة التسجيل <a href="https://academy.hsoub.com/programming/python/%D9%83%D9%8A%D9%81-%D8%AA%D8%B3%D8%AA%D8%AE%D8%AF%D9%85-%D8%A7%D9%84%D8%AA%D8%B3%D8%AC%D9%8A%D9%84-logging-%D9%81%D9%8A-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-3-r546/" rel=""><code>logging</code> من مكاتب بايثون القياسية</a> التي تتيح تعريف قاموس مخصص للتسجيل بالتفاصيل والصيغ التي نريدها وذلك باستخدام الدالة <a href="https://docs.python.org/3/library/logging.config.html#logging-config-dictschema" rel="external nofollow"><code>logging.config.dictConfig</code></a>.
</p>

<p>
	لنبدأ التطبيق العملي! افتح الملف <code>django-polls/mysite/settings.py</code> عبر محرر النصوص، واكتب ضمنه تعليمة <code>import</code> إضافية خاصة بـ <code>logging.config</code> ستسمح لك بتجاوز سلوك التسجيل الإفتراضي لتطبيق جانغو عبر تمرير قاموس تسجيل الأحداث التي ترغب بها للدالة <code>dictConfig</code>، كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5785_46" style="">
<span class="kwd">import</span><span class="pln"> json
</span><span class="kwd">import</span><span class="pln"> os
</span><span class="kwd">import</span><span class="pln"> logging</span><span class="pun">.</span><span class="pln">config
</span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span></pre>

<p>
	ثم انتقل إلى نهاية الملف والصق الكود التالي الخاص بإعدادات تسجيل الأحداث:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5785_54" style="">
<span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln">
</span><span class="com"># Logging Configuration</span><span class="pln">

</span><span class="com"># Clear prev config</span><span class="pln">
LOGGING_CONFIG </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">None</span><span class="pln">

</span><span class="com"># Get loglevel from env</span><span class="pln">
LOGLEVEL </span><span class="pun">=</span><span class="pln"> os</span><span class="pun">.</span><span class="pln">getenv</span><span class="pun">(</span><span class="str">'DJANGO_LOGLEVEL'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'info'</span><span class="pun">).</span><span class="pln">upper</span><span class="pun">()</span><span class="pln">

logging</span><span class="pun">.</span><span class="pln">config</span><span class="pun">.</span><span class="pln">dictConfig</span><span class="pun">({</span><span class="pln">
    </span><span class="str">'version'</span><span class="pun">:</span><span class="pln"> </span><span class="lit">1</span><span class="pun">,</span><span class="pln">
    </span><span class="str">'disable_existing_loggers'</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">False</span><span class="pun">,</span><span class="pln">
    </span><span class="str">'formatters'</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="str">'console'</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            </span><span class="str">'format'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'%(asctime)s %(levelname)s [%(name)s:%(lineno)s] %(module)s %(process)d %(thread)d %(message)s'</span><span class="pun">,</span><span class="pln">
        </span><span class="pun">},</span><span class="pln">
    </span><span class="pun">},</span><span class="pln">
    </span><span class="str">'handlers'</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="str">'console'</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            </span><span class="str">'class'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'logging.StreamHandler'</span><span class="pun">,</span><span class="pln">
            </span><span class="str">'formatter'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'console'</span><span class="pun">,</span><span class="pln">
        </span><span class="pun">},</span><span class="pln">
    </span><span class="pun">},</span><span class="pln">
    </span><span class="str">'loggers'</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="str">''</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            </span><span class="str">'level'</span><span class="pun">:</span><span class="pln"> LOGLEVEL</span><span class="pun">,</span><span class="pln">
            </span><span class="str">'handlers'</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="str">'console'</span><span class="pun">,],</span><span class="pln">
        </span><span class="pun">},</span><span class="pln">
    </span><span class="pun">},</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	لنناقش التعليمات السابقة: أعطينا المحدد <code>LOGGING_CONFIG</code> القيمة <code>none</code> وذلك لتعطيل النمط الإفتراضي لتسجيل جانغو، ومنحنا <code>LOGLEVEL</code> قيمة افتراضية هي <code>info</code> مع ترك المجال مفتوح لتجاوزها عند الحاجة بتمرير أي قيمة أخرى نريدها عن طريق متغير البيئة <code>DJANGO_LOGLEVEL</code>.
</p>

<p>
	وأخيرًا استخدمنا الدالة <code>dictConfig</code> لتعيين قاموس تسجيل الأحداث المرغوب الذي ستعتمده الوحدة <code>logging.config</code>، حددنا في القاموس طريقة تنسيق النص عبر <code>formatters</code> بحيث يتضمن مستوى خطورة الحدث ونوع الخط وغيرها من التفضيلات، كذلك حددنا الخرج عن طريق إعداد <code>handlers</code>، ومن ثم وضحنا كل سوية حدث بأي handler ترتبط باستخدام المسجلين <code>loggers</code>.
</p>

<p>
	الضبط الذي أجريناه هو الحد الأدنى المقبول من ضبط سجلات الأحداث فهو يمكننا من تحديد سوية خطورة معينة للأحداث عبر المتغير <code>DJANGO_LOGLEVEL</code> ما يجعل جانغو يسجل الأحداث بنماذج قياسية يمكن للحاويات التعامل معها وعرضها عبر <code>docker logs</code> أو <code>kubectl logs</code> تبعًا لنوع الحاوية، وإن رغبت بتفاصيل أعمق استرشد <a href="https://docs.djangoproject.com/en/2.2/topics/logging/" rel="external nofollow">بتوثيقات جانغو حول سجلات الأحداث</a>.
</p>

<p>
	ننهي بذلك التعديلات البرمجية على تطبيق جانغو الذي يدعى Polls وسنبدأ بتهيئة الحاوية عبر ملف Dockerfile في الخطوة التالية.
</p>

<h2>
	الخطوة 6: كتابة الملف Dockerfile لحاوية التطبيق
</h2>

<p>
	نقدم لك في هذه الفقرة إحدى أبسط الطرق لبناء صورة الحاوية container image اللازمة لتشغيل تطبيق جانغو مع خادم Gunicorn WSGI الخاص به، وكل ما يلزم العملية من إعدادات، مع العلم بوجود طرق أخرى يمكنك اتباعها.
</p>

<h3>
	اختيار صورة الحاوية المناسبة للبناء عليها
</h3>

<p>
	يوجد طريقتين لبناء صورة الحاوية إما أن تستخدم <code>scratch</code> الذي يوفر صورًا فارغة لا تحتوي أي نظام ملفات وتبني حاويتك من الصفر أو أن تعتمد صورة حاوية جاهزة، وهنا أيضًا لدينا العديد من الخيارات ولكلٍ منها متطلباته الخاصة من حيث الحزم المطلوبة ونسخة نظام التشغيل كذلك لغة البرمجة وغيرها، لذا يعد اختيار الصورة قرارًا مهمًا ويحتاج لبعض الدراسة.
</p>

<p>
	اختيار إحدى صور الحاويات الموجودة في <a href="https://hub.docker.com/explore/" rel="external nofollow">مستودعات دوكر</a> يعدّ خيارًا مناسبًا فهي مختبرة وفق أفضل الممارسات ويتم تحديثها بانتظام لتطويرها ومعالجة الثغرات الأمنية فيها، ويمكنك استعراض <a href="https://hub.docker.com/_/python/" rel="external nofollow">صور الحاويات المعتمدة على لغة بايثون</a> من ذلك المستودع فهي متوافقة بالتأكيد مع جانغو المطور بنفس اللغة، ولاحظ أن لكل صورة خصائص معينة من حيث إصدار بايثون وأدواته كذلك نسخة نظام التشغيل، ويمكنك اختيار الصورة الأنسب لبيئتك من جهتنا فضلنا صور <a href="https://alpinelinux.org/" rel="external nofollow">Alpine Linux</a> فهي توفر بيئة تشغيل قوية على الرغم من بساطتها وصغر حجم نظام الملفات الخاص بها إلا أنها تتضمن نظامًا كاملًا لإدارة الحزم ومستودعات برمجية متنوعة ما يسهل عليك إضافة الوظائف التي يمكن أن تحتاجها.
</p>

<p>
	<strong>تنويه</strong>: ستلاحظ أثناء استعراضك صور الحاويات المعتمدة على لغة بايثون أن كل صورة تحمل أكثر من وسم tag وأن الوسوم نفسها توضع على صورٍ مختلفة، فمثلًا الوسم 3alpine يشير إلى تثبيت أحدث إصدار من بايثون 3 على أحدث إصدار من Alpine وفي حال نزل إصدار جديد من أحدهما فسيُعاد تعيين الوسم على الصورة الجديدة أيضًا، لذا اختر الصورة التي تحمل الوسوم الأكثر تحديدًا لتحصل على أقرب صورة للبيئة التي ترغب بتحقيقها، وفي مثالنا الحالي سنستخدم الصورة ذات الوسم <code>3.7.4-alpine3.10</code>.
</p>

<p>
	سنحدد المستودع ووسم الصورة في ملف Dockerfile الخاص بتطبيقنا وذلك باستخدام التعليمة <code>from</code>، لنخرج أولًا من مجلد التطبيق <code>django-polls</code> حيث كنا نطبق التعليمات في الخطوات السابقة وذلك وفق الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5785_50" style="">
<span class="pln">cd </span><span class="pun">..</span></pre>

<p>
	أصبحنا الآن في المجلد الأب <code>polls-project</code> لننشئ ضمنه ملف نصي باستخدام المحرر ونسميه Dockerfile ونكتب فيه التعليمة التالية التي تعدّ نقطة البداية في إنشاء صورة الحاوية التي نجهزها للتطبيق:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5785_52" style="">
<span class="pln">FROM python</span><span class="pun">:</span><span class="lit">3.7</span><span class="pun">.</span><span class="lit">4</span><span class="pun">-</span><span class="pln">alpine3</span><span class="pun">.</span><span class="lit">10</span></pre>

<h3>
	كتابة التعليمات التنفيذية الخاصة بإعداد التطبيق للحاوية
</h3>

<p>
	تتضمن هذه الفقرة إضافة التعليمات التنفيذية وبعض الاعتماديات اللازمة وتهيئة بيئة التشغيل، قد تجد بعض أوجه التشابه التي تجمعها مع خطوات إعداد خادم التطبيق باستثناء بعض المفاهيم الخاصة بالحاويات.
</p>

<p>
	نضيف الكود التالي إلى الملف Dockerfile بعد تعليمة <code>from</code> السابقة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5785_56" style="">
<span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln">

ADD django</span><span class="pun">-</span><span class="pln">polls</span><span class="pun">/</span><span class="pln">requirements</span><span class="pun">.</span><span class="pln">txt </span><span class="pun">/</span><span class="pln">app</span><span class="pun">/</span><span class="pln">requirements</span><span class="pun">.</span><span class="pln">txt

RUN set </span><span class="pun">-</span><span class="pln">ex \
    </span><span class="pun">&amp;&amp;</span><span class="pln"> apk add </span><span class="pun">--</span><span class="pln">no</span><span class="pun">-</span><span class="pln">cache </span><span class="pun">--</span><span class="pln">virtual </span><span class="pun">.</span><span class="pln">build</span><span class="pun">-</span><span class="pln">deps postgresql</span><span class="pun">-</span><span class="pln">dev build</span><span class="pun">-</span><span class="pln">base \
    </span><span class="pun">&amp;&amp;</span><span class="pln"> python </span><span class="pun">-</span><span class="pln">m venv </span><span class="pun">/</span><span class="pln">env \
    </span><span class="pun">&amp;&amp;</span><span class="pln"> </span><span class="pun">/</span><span class="pln">env</span><span class="pun">/</span><span class="pln">bin</span><span class="pun">/</span><span class="pln">pip install </span><span class="pun">--</span><span class="pln">upgrade pip \
    </span><span class="pun">&amp;&amp;</span><span class="pln"> </span><span class="pun">/</span><span class="pln">env</span><span class="pun">/</span><span class="pln">bin</span><span class="pun">/</span><span class="pln">pip install </span><span class="pun">--</span><span class="pln">no</span><span class="pun">-</span><span class="pln">cache</span><span class="pun">-</span><span class="pln">dir </span><span class="pun">-</span><span class="pln">r </span><span class="pun">/</span><span class="pln">app</span><span class="pun">/</span><span class="pln">requirements</span><span class="pun">.</span><span class="pln">txt \
    </span><span class="pun">&amp;&amp;</span><span class="pln"> runDeps</span><span class="pun">=</span><span class="str">"$(scanelf --needed --nobanner --recursive /env \
        | awk '{ gsub(/,/, "</span><span class="pln">\nso</span><span class="pun">:</span><span class="str">", $2); print "</span><span class="pln">so</span><span class="pun">:</span><span class="str">" $2 }' \
        | sort -u \
        | xargs -r apk info --installed \
        | sort -u)"</span><span class="pln"> \
    </span><span class="pun">&amp;&amp;</span><span class="pln"> apk add </span><span class="pun">--</span><span class="pln">virtual rundeps $runDeps \
    </span><span class="pun">&amp;&amp;</span><span class="pln"> apk </span><span class="kwd">del</span><span class="pln"> </span><span class="pun">.</span><span class="pln">build</span><span class="pun">-</span><span class="pln">deps

ADD django</span><span class="pun">-</span><span class="pln">polls </span><span class="pun">/</span><span class="pln">app
WORKDIR </span><span class="pun">/</span><span class="pln">app

ENV VIRTUAL_ENV </span><span class="pun">/</span><span class="pln">env
ENV PATH </span><span class="pun">/</span><span class="pln">env</span><span class="pun">/</span><span class="pln">bin</span><span class="pun">:</span><span class="pln">$PATH

EXPOSE </span><span class="lit">8000</span></pre>

<p>
	سنقدم فيما يلي شرحًا لبعض ما ورد ضمن الكود السابق ولمزيد من المعلومات يمكنك الاطلاع على <a href="https://www.caktusgroup.com/blog/2017/03/14/production-ready-dockerfile-your-python-django-app/" rel="external nofollow">ملف Dockerfile جاهز لتطبيق جانغو</a>.
</p>

<p>
	تتضمن التعليمة الأولى نسخ الملف requirements.txt إلى المسار <code>app/requirements.txt/</code> بحيث تصبح اعتماديات التطبيق اللازمة موجودة ضمن نظام ملفات الصورة ليتم استخدامها في تنزيل كافة حزم بايثون الضرورية لعمل التطبيق، لاحظ أننا وضعنا هذه التعليمة في خطوة منفصلة وذلك ليقوم دوكر بتخزين الصورة التي تتضمن requirements.txt في طبقة منفصلة ما يتيح له إعادة استخدامها عند الحاجة عوضًا عن بنائها من جديد (بالطبع على فرض أن الاعتماديات لم تتغير)، ذلك أن دوكر ينشئ طبقة للصورة على نظام الملفات بعد تنفيذ كل تعليمة <code>ADD</code> أو <code>RUN</code> أو <code>COPY</code>.
</p>

<p>
	أما التعليمة الثانية <code>RUN</code> فهي تشتمل على مجموعة من أوامر لينكس يجمع بينها الإشارة <code>&amp;&amp;</code> لتنفيذها على التوالي نوردها بإيجاز:
</p>

<ul>
<li>
		تثبيت PostgreSQL وبناء اعتماديتها باستخدام <code>apk</code> مدير حزم Alpine.
	</li>
	<li>
		إنشاء البيئة الافتراضية.
	</li>
	<li>
		تثبيت اعتماديات بايثون المذكورة في الملف requirements.txt وذلك باستخدام <code>pip</code>.
	</li>
	<li>
		إعداد قائمة بالحزم المطلوبة للتشغيل من خلال تحليل متطلبات حزم بايثون المثبتة.
	</li>
	<li>
		إزالة تثبيت أي اعتماديات مبنية ولم تعد لازمة.
	</li>
</ul>
<p>
	جمعنا الأوامر السابقة بتعليمة <code>RUN</code> واحدة حتى يجمعها دوكر في طبقة واحدة ما يخفف عدد الطبقات المنشأة أثناء العمل، وحيث أن حذف العناصر المنشأة ضمن طبقة صورة معينة لن يكون متاحًا خارجها في طبقات لاحقة، فقد وضعنا الأمر <code>del</code> ضمن التعليمة <code>Run</code> نفسها التي تتضمن بناء الاعتماديات واستخدامها لتثبيت حزم التطبيق، ومن ثم حذفها عبر <code>apk del</code>، وذلك بهدف توفير المساحات التخزينية.
</p>

<p>
	أما التعليمة الثالثة بعد <code>Run</code> كانت <code>ADD</code> وهي تهدف إلى نسخ كود التطبيق ومسار العمل <code>WORKDIR</code> وذلك لضبط مسار العمل في الصورة على مسار التطبيق.
</p>

<p>
	تعرّف التعليمة الرابعة <code>ENV</code> اثنين من متغيرات البيئة التي ستكون متاحة ضمن الحاوية، المتغير الأول هو <code>VIRTUAL_ENV</code> ويأخذ القيمة <code>env/</code> أما المتغير الثاني <code>PATH</code> يضبط على المسار <code>env/bin/</code>، وتحاكي هذه العملية الطريقة التقليدية المتبعة لتفعيل البيئة الافتراضية للتطبيق عبر سكريبت <code>env/bin/activate/</code>.
</p>

<p>
	أما العملية الأخيرة في هذه المرحلة فهي تحديد البوابة 8000 للاتصال مع الحاوية في وقت التشغيل عبر تعليمة <code>EXPOSE</code>.
</p>

<p>
	شارف الملف Dockerfile الاكتمال بما نفذناه حتى الآن والخطوة المتبقية له هي تحديد الأمر الذي سيُنفذ افتراضيًا عندما تبدأ الحاوية باستخدام الصورة أي عند بدء تشغيل الحاوية.
</p>

<h3>
	تحديد الأمر الافتراضي
</h3>

<p>
	يُكتب الأمر الافتراضي لحاوية دوكر في الملف Dockerfile باستخدام تعليمة <code>ENTRYPOINT</code> أو تعليمة <code>CMD</code> أو كلاهما معًا، ويحدد هذا الأمر ما سيُنفذ افتراضيًا عند بدء تشغيل الحاوية.
</p>

<p>
	في حال استخدمنا <code>ENTRYPOINT</code> و <code>CMD</code> معًا فإن الأولى ستحدد الأمر التنفيذي (الإجراء) الذي سينفذ عند تشغيل الحاوية والثانية ستحدد قائمة الوسطاء arguments اللازمة له.
</p>

<p>
	علمًا أن المستخدم يستطيع بسهولة تجاوز الوسطاء المعرّفين في <code>CMD</code> وذلك بتمرير وسطاء غيرهم لأمر تشغيل الحاوية كما يلي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_5785_58" style="">
<span class="pln">docker run </span><span class="tag">&lt;image&gt;</span><span class="pln"> </span><span class="tag">&lt;arguments&gt;</span></pre>

<p>
	بينما لا يمكنه بنفس السهولة تجاوز الأمر التنفيذي الوارد في <code>ENTRYPOINT</code> لذا يلجأ الأغلبية لجعل الأمر التنفيذي المعطى لـ <code>ENTRYPOINT</code> على هيئة سكريبت يهيئ البيئة وينفذ الإجراءات التي تتناسب مع قائمة الوسطاء التي يتلقاها.
</p>

<p>
	ذكرنا فيما سبق طريقة استخدام <code>ENTRYPOINT</code> و <code>CMD</code> معًا، أما لو أردنا استخدام إحداهما فقط فسيكون الوضع كالتالي:
</p>

<ul>
<li>
		استخدام <code>ENTRYPOINT</code> لوحدها يحدد الأمر التنفيذي للحاوية فقط دون تحديد قائمة الوسطاء.
	</li>
	<li>
		أما استخدام <code>CMD</code> لوحدها يتيح تحديد الأمر command والوسطاء arguments مع إمكانية تجاوز الوسطاء لاحقًا عبر <code>docker run</code> كما أسلفنا.
	</li>
</ul>
<p>
	لنطبق عمليًا على الصورة التي نبنيها ونناقش المتطلبات التي ستقودنا لاختيار التعليمة المناسبة لبدء التشغيل.
</p>

<p>
	نريد للتطبيق أن يعمل افتراضيًا باستخدام الخادم <code>gunicorn</code> أما تقديم قائمة الوسطاء لعمل <code>gunicorn</code> فهو غير ملزم في مرحلة بدء التشغيل، كما أننا نفضل بقاء الفرصة متاحة لتشغيل أوامر أخرى بسهولة عند بدء تشغيل الحاوية مثل عملية تنقيح الأخطاء debugging أو بعض المهام الإدارية (جمع المكونات الساكنة للتطبيق أو تهيئة قاعدة البيانات أو غيرها)، كل ذلك يدفعنا لاستخدام <code>CMD</code> بمفردها دون الحاجة لتعليمة <code>ENTRYPOINT</code>، لنتعرف إذًا على صيغ كتابتها.
</p>

<p>
	تكتب <code>CMD</code> عمومًا بثلاث صيغ:
</p>

<ol>
<li>
		تمرير الوسطاء لتعليمة <code>ENTRYPOINT</code>:
	</li>
</ol>
<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5785_64" style="">
<span class="pln">CMD </span><span class="pun">[</span><span class="str">"argument 1"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"argument 2"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">,</span><span class="str">"argument n"</span><span class="pun">]</span></pre>

<ol start="2">
<li>
		بصيغة <code>exec</code>:
	</li>
</ol>
<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5785_62" style="">
<span class="pln">CMD </span><span class="pun">[</span><span class="str">"command"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"argument 1"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"argument 2"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">,</span><span class="str">"argument n"</span><span class="pun">]</span></pre>

<ol start="3">
<li>
		بصيغة shell:
	</li>
</ol>
<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5785_66" style="">
<span class="pln">CMD command </span><span class="str">"argument 1"</span><span class="pln"> </span><span class="str">"argument 2"</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="str">"argument n"</span></pre>

<p>
	تستخدم <code>CMD</code> في الصيغة الأولى مع <code>ENTRYPOINT</code> لتمرير قائمة الوسطاء فقط كما ذكرنا، أما في الصيغتين الثانية والثالثة تكون مسؤولة عن تحديد الأمر الذي سينفذ وكذلك الوسطاء، والفرق بينهما أن صيغة <code>exec</code> (وهي الصيغة الموصى بها) تنفذ الأمر المحدد مباشرةً وتمرر قائمة الوسطاء دون القيام بأي عمليات أو معالجة في <a href="https://academy.hsoub.com/devops/linux/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D9%83%D8%AA%D8%A7%D8%A8%D8%A9-%D8%B3%D9%83%D8%B1%D8%A8%D8%AA%D8%A7%D8%AA-%D8%A7%D9%84%D8%B5%D8%AF%D9%81%D8%A9-shell-scripts-r252/" rel="">الصدفة shell</a>، أما في صيغة الصدفة shell فإن قائمة الوسطاء تُمرر إلى <code>sh -c</code>، وقد تكون ضرورية لك في الحالات التي تحتاج فيها لتبديل أحد متغيرات البيئة التي يستخدمها الأمر.
</p>

<p>
	بالنتيجة اعتمدنا الشكل النهائي التالي لتحديد افتراضات تشغيل الصورة، وكُتب ضمن Dockerfile كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5785_68" style="">
<span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln">
CMD </span><span class="pun">[</span><span class="str">"gunicorn"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"--bind"</span><span class="pun">,</span><span class="pln"> </span><span class="str">":8000"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"--workers"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"3"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"mysite.wsgi:application"</span><span class="pun">]</span></pre>

<p>
	وبموجبه تشغل الحاوية <code>gunicorn</code> على الجهاز المحلي localhost افتراضيًا وذلك عبر البوابة 8000 باستخدام عمال workers عدد 3، كما أنها تشغل الدالة <code>application</code> الموجودة ضمن الملف <code>wsgi.py</code> في المسار <code>mysite</code>.
</p>

<p>
	وبذلك أصبحنا جاهزين للمرحلة التالية وهي استخدام الأمر <code>docker build</code> لبناء صورة التطبيق والأمر <code>docker run</code> لتشغيل الحاوية.
</p>

<h3>
	بناء صورة دوكر
</h3>

<p>
	يبني <code>docker build</code> الصورة باستخدام كل من ملف dockerfile الذي يتضمن إعدادات البناء، وسياق "context" الذي يشير إلى مسار مجموعة الملفات التي يجب أن تتوفر لعفريت دوكر Docker daemon لينجز عملية البناء.
</p>

<p>
	المتغير "context" يطابق في معظم الأحيان المسار الحالي الذي نكون فيه عند تنفيذ الأمر <code>docker build</code> ويُشار إلى المسار الحالي في لينكس برمز النقطة <code>.</code> .
</p>

<p>
	للتنفيذ العملي، ابحث عن مسار الملف Dockerfile ونفذ ضمنه التالي مع تمرير اسم الصورة بعد الراية <code>t-</code> بالإضافة إلى تمرير المسار الحالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5785_70" style="">
<span class="pln">docker build </span><span class="pun">-</span><span class="pln">t django</span><span class="pun">-</span><span class="pln">polls</span><span class="pun">:</span><span class="pln">v0 </span><span class="pun">.</span></pre>

<p>
	لاحظ أننا أعطينا الاسم <code>django-polls</code> للصورة أما <code>v0</code> فهي تشير للإصدار رقم 0 من الصورة، حيث أن عفريت دوكر الذي يعمل في الخلفية يبني الصورة عبر سلسلة من الطبقات بناءً على بنية تعليمات ملف Dockerfile.
</p>

<p>
	سنحصل على الخرج التالي فور إكتمال تنفيذ الأمر <code>docker build</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5785_72" style="">
<span class="typ">Successfully</span><span class="pln"> built </span><span class="lit">8260b58f5713</span><span class="pln">
</span><span class="typ">Successfully</span><span class="pln"> tagged django</span><span class="pun">-</span><span class="pln">polls</span><span class="pun">:</span><span class="pln">v0</span></pre>

<p>
	نظريًا يمكنك تشغيل الحاوية عبر الأمر <code>docker run</code> بعد انتهاء عملية البناء بنجاح، ولكن في الواقع سيفشل الأمر <code>run</code> ما لم تعرف داخل الحاوية كافة متغيرات البيئة التي حددناها خارجيًا في الخطوات السابقة مثل <code>SECRET_KEY</code> ومحددات قاعدة البيانات ضمن الملف <code>setting.py</code> وغيرها، وهو ما سنقوم به في الخطوة التالية.
</p>

<h2>
	الخطوة 7: ضبط متغيرات بيئة التشغيل واختبار التطبيق
</h2>

<p>
	يوفر دوكر <a href="https://docs.docker.com/engine/reference/commandline/run/#set-environment-variables--e---env---env-file" rel="external nofollow">عدة طرق</a> لتعريف المتغيرات داخل الحاوية، ولكون حالتنا تتطلب إعادة تعريف كامل المتغيرات التي عملنا عليها في الخطوة /1/، سنتبع الطريقة <code>env-file</code> التي تتيح تمرير ملف يتضمن كامل متغيرات البيئة وقيمة كل منها.
</p>

<p>
	لتنفيذ ذلك سننشئ ملفًا نصيًا يدعى <code>env</code> ضمن المجلد <code>polls-project</code> مع لصق القائمة التالية ضمنه وهي تحتوي كامل المتغيرات وقيمها:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5785_76" style="">
<span class="pln">DJANGO_SECRET_KEY</span><span class="pun">=</span><span class="pln">your_secret_key
DEBUG</span><span class="pun">=</span><span class="typ">True</span><span class="pln">
DJANGO_ALLOWED_HOSTS</span><span class="pun">=</span><span class="pln">your_server_IP_address
DATABASE_ENGINE</span><span class="pun">=</span><span class="pln">postgresql_psycopg2
DATABASE_NAME</span><span class="pun">=</span><span class="pln">polls
DATABASE_USERNAME</span><span class="pun">=</span><span class="pln">sammy
DATABASE_PASSWORD</span><span class="pun">=</span><span class="pln">your_database_password
DATABASE_HOST</span><span class="pun">=</span><span class="pln">your_database_host
DATABASE_PORT</span><span class="pun">=</span><span class="pln">your_database_port
STATIC_ACCESS_KEY_ID</span><span class="pun">=</span><span class="pln">your_space_access_key_id
STATIC_SECRET_KEY</span><span class="pun">=</span><span class="pln">your_space_secret_key
STATIC_BUCKET_NAME</span><span class="pun">=</span><span class="pln">your_space_name
STATIC_ENDPOINT_URL</span><span class="pun">=</span><span class="pln">https</span><span class="pun">:</span><span class="com">//nyc3.digitaloceanspaces.com</span><span class="pln">
DJANGO_LOGLEVEL</span><span class="pun">=</span><span class="pln">info</span></pre>

<p>
	لا تنسَ تغيير القيم المطلوبة بما يناسب إعداداتك وفق التالي:
</p>

<ol>
<li>
		بالنسبة للمفتاح السري <code>DJANGO_SECRET_KEY</code> اختر له قيمة فريدة صعبة التخمين كما توصي <a href="https://docs.djangoproject.com/en/2.2/ref/settings/#secret-key" rel="external nofollow">توثيقات دوكر</a> تقدم لك الخطوة /5/ من مقال <a href="https://www.digitalocean.com/community/tutorials/how-to-set-up-a-scalable-django-app-with-digitalocean-managed-databases-and-spaces#step-5-adjusting-the-app-settings" rel="external nofollow">كيفية إعداد تطبيق جانغو عبر DigitalOcean Managed Databases and Spaces</a> الأجنبي إحدى طرق جانغو لتوليد المفاتيح.
	</li>
	<li>
		ضع عنوان IP الخاص بالخادم أوبونتو مقابل <code>DJANGO_ALLOWED_HOSTS</code> ويمكنك وضع الرمز <code>*</code> لأغراض الاختبار فقط وليس ضمن بيئة العمل الفعلية فهو يسمح باتصال أي عنوان IP.
	</li>
	<li>
		اكتب اسم مستخدم قاعدة البيانات وكلمة المرور كما حددتهما في الخطوات السابقة مقابل <code>DATABASE_USERNAME</code> و <code>DATABASE_PASSWORD</code>.
	</li>
	<li>
		كذلك الأمر بالنسبة لاعدادات الاتصال مع قاعدة البيانات من حيث اسم مضيف قاعدة البيانات وبوابة اتصال قاعدة البيانات ضع هذه البيانات مقال كل من <code>DATABASE_HOST</code> و <code>DATABASE_PORT</code>.
	</li>
	<li>
		أما المحددات <code>STATIC_ACCESS_KEY_ID</code> و <code>STATIC_SECRET_KEY</code> و <code>STATIC_BUCKET_NAME</code> و <code>STATIC_ENDPOINT_URL</code> فهي تتعلق بخدمة التخزين الكائني الخارجية، اضبطها بما يتناسب مع الخدمة التي تستخدمها.
	</li>
</ol>
<p>
	قبل تشغيل الأمر <code>docker run</code> احفظ التغيرات على الملف السابق env وتأكد أن المحدد <code>DEBUG</code> يحمل القيمة <code>false</code> وأن سجلات الأحداث مضبوطة بحيث تعطي التفاصيل المرغوبة.
</p>

<p>
	وبعدها نفذ أمر التشغيل للإصدار v0 من الصورة django-polls بطريقة تتجاوز الإعدادات الافتراضية التي حددناها سابقًا ضمن الملف Dockerfile (باستخدام CMD) وتنشئ أثناء التشغيل مخطط قاعدة البيانات الخاص بالتطبيق عبر <code>manage.py makemigrations</code> و <code>manage.py migrate</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5785_82" style="">
<span class="pln">docker run </span><span class="pun">--</span><span class="pln">env</span><span class="pun">-</span><span class="pln">file env django</span><span class="pun">-</span><span class="pln">polls</span><span class="pun">:</span><span class="pln">v0 sh </span><span class="pun">-</span><span class="pln">c </span><span class="str">"python manage.py makemigrations &amp;&amp; python manage.py migrate"</span></pre>

<p>
	سيظهر الخرج التالي بعد التنفيذ:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5785_84" style="">
<span class="typ">No</span><span class="pln"> changes detected
</span><span class="typ">Operations</span><span class="pln"> to perform</span><span class="pun">:</span><span class="pln">
  </span><span class="typ">Apply</span><span class="pln"> all migrations</span><span class="pun">:</span><span class="pln"> admin</span><span class="pun">,</span><span class="pln"> auth</span><span class="pun">,</span><span class="pln"> contenttypes</span><span class="pun">,</span><span class="pln"> polls</span><span class="pun">,</span><span class="pln"> sessions
</span><span class="typ">Running</span><span class="pln"> migrations</span><span class="pun">:</span><span class="pln">
  </span><span class="typ">Applying</span><span class="pln"> contenttypes</span><span class="pun">.</span><span class="lit">0001</span><span class="pln">_initial</span><span class="pun">...</span><span class="pln"> OK
  </span><span class="typ">Applying</span><span class="pln"> auth</span><span class="pun">.</span><span class="lit">0001</span><span class="pln">_initial</span><span class="pun">...</span><span class="pln"> OK
  </span><span class="typ">Applying</span><span class="pln"> admin</span><span class="pun">.</span><span class="lit">0001</span><span class="pln">_initial</span><span class="pun">...</span><span class="pln"> OK
  </span><span class="typ">Applying</span><span class="pln"> admin</span><span class="pun">.</span><span class="lit">0002</span><span class="pln">_logentry_remove_auto_add</span><span class="pun">...</span><span class="pln"> OK
  </span><span class="typ">Applying</span><span class="pln"> admin</span><span class="pun">.</span><span class="lit">0003</span><span class="pln">_logentry_add_action_flag_choices</span><span class="pun">...</span><span class="pln"> OK
  </span><span class="typ">Applying</span><span class="pln"> contenttypes</span><span class="pun">.</span><span class="lit">0002</span><span class="pln">_remove_content_type_name</span><span class="pun">...</span><span class="pln"> OK
  </span><span class="typ">Applying</span><span class="pln"> auth</span><span class="pun">.</span><span class="lit">0002</span><span class="pln">_alter_permission_name_max_length</span><span class="pun">...</span><span class="pln"> OK
  </span><span class="typ">Applying</span><span class="pln"> auth</span><span class="pun">.</span><span class="lit">0003</span><span class="pln">_alter_user_email_max_length</span><span class="pun">...</span><span class="pln"> OK
  </span><span class="typ">Applying</span><span class="pln"> auth</span><span class="pun">.</span><span class="lit">0004</span><span class="pln">_alter_user_username_opts</span><span class="pun">...</span><span class="pln"> OK
  </span><span class="typ">Applying</span><span class="pln"> auth</span><span class="pun">.</span><span class="lit">0005</span><span class="pln">_alter_user_last_login_null</span><span class="pun">...</span><span class="pln"> OK
  </span><span class="typ">Applying</span><span class="pln"> auth</span><span class="pun">.</span><span class="lit">0006</span><span class="pln">_require_contenttypes_0002</span><span class="pun">...</span><span class="pln"> OK
  </span><span class="typ">Applying</span><span class="pln"> auth</span><span class="pun">.</span><span class="lit">0007</span><span class="pln">_alter_validators_add_error_messages</span><span class="pun">...</span><span class="pln"> OK
  </span><span class="typ">Applying</span><span class="pln"> auth</span><span class="pun">.</span><span class="lit">0008</span><span class="pln">_alter_user_username_max_length</span><span class="pun">...</span><span class="pln"> OK
  </span><span class="typ">Applying</span><span class="pln"> auth</span><span class="pun">.</span><span class="lit">0009</span><span class="pln">_alter_user_last_name_max_length</span><span class="pun">...</span><span class="pln"> OK
  </span><span class="typ">Applying</span><span class="pln"> auth</span><span class="pun">.</span><span class="lit">0010</span><span class="pln">_alter_group_name_max_length</span><span class="pun">...</span><span class="pln"> OK
  </span><span class="typ">Applying</span><span class="pln"> auth</span><span class="pun">.</span><span class="lit">0011</span><span class="pln">_update_proxy_permissions</span><span class="pun">...</span><span class="pln"> OK
  </span><span class="typ">Applying</span><span class="pln"> polls</span><span class="pun">.</span><span class="lit">0001</span><span class="pln">_initial</span><span class="pun">...</span><span class="pln"> OK
  </span><span class="typ">Applying</span><span class="pln"> sessions</span><span class="pun">.</span><span class="lit">0001</span><span class="pln">_initial</span><span class="pun">...</span><span class="pln"> OK</span></pre>

<p>
	ننفذ بعدها أمر تشغيل جديد لحاوية التطبيق نستدعي خلاله سطر الأوامر الخاص بالصدفة Shell كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5785_86" style="">
<span class="pln">docker run </span><span class="pun">-</span><span class="pln">i </span><span class="pun">-</span><span class="pln">t </span><span class="pun">--</span><span class="pln">env</span><span class="pun">-</span><span class="pln">file env django</span><span class="pun">-</span><span class="pln">polls</span><span class="pun">:</span><span class="pln">v0 sh</span></pre>

<p>
	تظهر عندها نافذة صدفة shell ضمن الحاوية وننشئ من خلالها المستخدم الذي يحمل صلاحية مدير تطبيق جانغو:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5785_89" style="">
<span class="pln">python manage</span><span class="pun">.</span><span class="pln">py createsuperuser</span></pre>

<p>
	يلي ذلك إدخال بيانات المستخدم وهي اسم المستخدم وكلمة المرور والبريد الإلكتروني، ومن ثم الخروج من الحاوية وإنهاء عملها باستخدام الأمر <code>Ctrl+D</code>.
</p>

<p>
	أما العملية الأخيرة هي جمع الملفات الساكنة الخاصة بالتطبيق <code>collectstatic</code> ورفعها على خدمة التخزين الكائني:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5785_91" style="">
<span class="pln">docker run </span><span class="pun">--</span><span class="pln">env</span><span class="pun">-</span><span class="pln">file env django</span><span class="pun">-</span><span class="pln">polls</span><span class="pun">:</span><span class="pln">v0 sh </span><span class="pun">-</span><span class="pln">c </span><span class="str">"python manage.py collectstatic --noinput"</span></pre>

<p>
	لنحصل على الخرج التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5785_93" style="">
<span class="lit">121</span><span class="pln"> static files copied</span><span class="pun">.</span></pre>

<p>
	يمكننا الآن تشغيل التطبيق بالإعدادات الافتراضية المحددة ضمن Dockerfile:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5785_95" style="">
<span class="pln">docker run </span><span class="pun">--</span><span class="pln">env</span><span class="pun">-</span><span class="pln">file env </span><span class="pun">-</span><span class="pln">p </span><span class="lit">80</span><span class="pun">:</span><span class="lit">8000</span><span class="pln"> django</span><span class="pun">-</span><span class="pln">polls</span><span class="pun">:</span><span class="pln">v0</span></pre>

<p>
	ونحصل على الخرج التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5785_97" style="">
<span class="pun">[</span><span class="lit">2019</span><span class="pun">-</span><span class="lit">10</span><span class="pun">-</span><span class="lit">17</span><span class="pln"> </span><span class="lit">21</span><span class="pun">:</span><span class="lit">23</span><span class="pun">:</span><span class="lit">36</span><span class="pln"> </span><span class="pun">+</span><span class="lit">0000</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="lit">1</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="pln">INFO</span><span class="pun">]</span><span class="pln"> </span><span class="typ">Starting</span><span class="pln"> gunicorn </span><span class="lit">19.9</span><span class="pun">.</span><span class="lit">0</span><span class="pln">
</span><span class="pun">[</span><span class="lit">2019</span><span class="pun">-</span><span class="lit">10</span><span class="pun">-</span><span class="lit">17</span><span class="pln"> </span><span class="lit">21</span><span class="pun">:</span><span class="lit">23</span><span class="pun">:</span><span class="lit">36</span><span class="pln"> </span><span class="pun">+</span><span class="lit">0000</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="lit">1</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="pln">INFO</span><span class="pun">]</span><span class="pln"> </span><span class="typ">Listening</span><span class="pln"> at</span><span class="pun">:</span><span class="pln"> http</span><span class="pun">://</span><span class="lit">0.0</span><span class="pun">.</span><span class="lit">0.0</span><span class="pun">:</span><span class="lit">8000</span><span class="pln"> </span><span class="pun">(</span><span class="lit">1</span><span class="pun">)</span><span class="pln">
</span><span class="pun">[</span><span class="lit">2019</span><span class="pun">-</span><span class="lit">10</span><span class="pun">-</span><span class="lit">17</span><span class="pln"> </span><span class="lit">21</span><span class="pun">:</span><span class="lit">23</span><span class="pun">:</span><span class="lit">36</span><span class="pln"> </span><span class="pun">+</span><span class="lit">0000</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="lit">1</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="pln">INFO</span><span class="pun">]</span><span class="pln"> </span><span class="typ">Using</span><span class="pln"> worker</span><span class="pun">:</span><span class="pln"> sync
</span><span class="pun">[</span><span class="lit">2019</span><span class="pun">-</span><span class="lit">10</span><span class="pun">-</span><span class="lit">17</span><span class="pln"> </span><span class="lit">21</span><span class="pun">:</span><span class="lit">23</span><span class="pun">:</span><span class="lit">36</span><span class="pln"> </span><span class="pun">+</span><span class="lit">0000</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="lit">7</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="pln">INFO</span><span class="pun">]</span><span class="pln"> </span><span class="typ">Booting</span><span class="pln"> worker </span><span class="kwd">with</span><span class="pln"> pid</span><span class="pun">:</span><span class="pln"> </span><span class="lit">7</span><span class="pln">
</span><span class="pun">[</span><span class="lit">2019</span><span class="pun">-</span><span class="lit">10</span><span class="pun">-</span><span class="lit">17</span><span class="pln"> </span><span class="lit">21</span><span class="pun">:</span><span class="lit">23</span><span class="pun">:</span><span class="lit">36</span><span class="pln"> </span><span class="pun">+</span><span class="lit">0000</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="lit">8</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="pln">INFO</span><span class="pun">]</span><span class="pln"> </span><span class="typ">Booting</span><span class="pln"> worker </span><span class="kwd">with</span><span class="pln"> pid</span><span class="pun">:</span><span class="pln"> </span><span class="lit">8</span><span class="pln">
</span><span class="pun">[</span><span class="lit">2019</span><span class="pun">-</span><span class="lit">10</span><span class="pun">-</span><span class="lit">17</span><span class="pln"> </span><span class="lit">21</span><span class="pun">:</span><span class="lit">23</span><span class="pun">:</span><span class="lit">36</span><span class="pln"> </span><span class="pun">+</span><span class="lit">0000</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="lit">9</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="pln">INFO</span><span class="pun">]</span><span class="pln"> </span><span class="typ">Booting</span><span class="pln"> worker </span><span class="kwd">with</span><span class="pln"> pid</span><span class="pun">:</span><span class="pln"> </span><span class="lit">9</span></pre>

<p>
	كما ذكرنا فإن التشغيل هذه المرة تم وفق الإعدادات الافتراضية ضمن ملف Dockerfile وهي كما حددناها سابقًا:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5785_103" style="">
<span class="pln">gunicorn </span><span class="pun">--</span><span class="pln">bind </span><span class="pun">:</span><span class="lit">8000</span><span class="pln"> </span><span class="pun">--</span><span class="pln">workers </span><span class="lit">3</span><span class="pln"> mysite</span><span class="pun">.</span><span class="pln">wsgi</span><span class="pun">:</span><span class="pln">application</span></pre>

<p>
	وبذلك فإن البوابة 80 الخاصة بالخادم أوبونتو ستوجه الحركة إلى البوابة 8000 في الحاوية <code>django-polls:v0</code>.
</p>

<p>
	يمكنك الآن فتح التطبيق polls عبر متصفح الإنترنت بكتابة عنوان URL الخاص به، وانتبه أنك ستحصل على الخطأ 404 (لم يتم العثور على الصفحة) في حال اكتفيت بالعنوان التالي مثلًا:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5785_101" style="">
<span class="pln"> http</span><span class="pun">:</span><span class="com">//your_server_ip/ </span></pre>

<p>
	بسبب عدم وجود بيانات للتطبيق تحت الجذر <code>/</code> ، لذا اكتب العنوان التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5785_105" style="">
<span class="pln">http</span><span class="pun">:</span><span class="com">//your_server_ip/polls</span></pre>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="111574" href="https://academy.hsoub.com/uploads/monthly_2022_11/01-img-polls_app.png.1a400371c5d5fc1869c302b858f6606d.png" rel=""><img alt="01-img-polls_app.png" class="ipsImage ipsImage_thumbnailed" data-fileid="111574" data-unique="qk9qk69e5" src="https://academy.hsoub.com/uploads/monthly_2022_11/01-img-polls_app.png.1a400371c5d5fc1869c302b858f6606d.png" style="width: 400px; height: auto;"></a>
</p>

<p>
	اكتب الرابط <code>http://your_server_ip/admin</code> للوصول إلى واجهة الدخول للوحة التحكم الخاصة بالتطبيق:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="111575" href="https://academy.hsoub.com/uploads/monthly_2022_11/02-img-polls_admin.png.f8bb9084da25a91b524eee5cdd27fe40.png" rel=""><img alt="واجهة الدخول للوحة التحكم" class="ipsImage ipsImage_thumbnailed" data-fileid="111575" data-unique="paublygfy" src="https://academy.hsoub.com/uploads/monthly_2022_11/02-img-polls_admin.png.f8bb9084da25a91b524eee5cdd27fe40.png" style="width: 532px; height: auto;"></a>
</p>

<p>
	أدخل بيانات المستخدم مدير التطبيق المنشأ سابقًا باستخدام الأمر <code>createsuperuser</code> وستظهر أمامك واجهة الإدارة والتحكم الخاصة بالتطبيق.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="111576" href="https://academy.hsoub.com/uploads/monthly_2022_11/03-img-polls_admin_main.png.586ca6a67195336d4edffae4c221f147.png" rel=""><img alt="واجهة الإدارة والتحكم الخاصة بالتطبيق" class="ipsImage ipsImage_thumbnailed" data-fileid="111576" data-unique="ywilvnwq9" src="https://academy.hsoub.com/uploads/monthly_2022_11/03-img-polls_admin_main.thumb.png.995fe1c788cbc2e1084117055b5a1ac2.png" style="width: 790px; height: auto;"></a>
</p>

<p>
	لاحظ أن تسليم المكونات الساكنة للتطبيق admin و polls يتم من خدمة التخزين الكائني ويمكنك اتباع اختبار جودة والتأكد من صحة إحضار الملفات.
</p>

<p>
	اضغط <code>Ctrl+c</code> في نافذة كتابة الأوامر السطرية التي تشغل حاوية دوكر لإنهاء عمل الحاوية.
</p>

<h2>
	خاتمة
</h2>

<p>
	استعرضنا في هذا المقال طريقة تكييف تطبيق جانغو ليعمل بفعالية ضمن بيئة تطوير سحابية أصلية cloud-native معتمدة على الحاويات container-based، وكتابة ملف Dockerfile محلي ومبسط لصورة الحاوية ومن ثم تشغيله عبر محرك دوكر، ويمكنك رؤية <a href="https://github.com/do-community/django-polls/commit/b182868d734238e44a64cd586c3dc2929da85973" rel="external nofollow">التغييرات</a> التي طبقناها وتأثيراتها باستعراض <a href="https://github.com/do-community/django-polls/tree/polls-docker" rel="external nofollow">قسم polls-docker</a> من مستودع GitHub فهو يتضمن كافة تعديلات التطبيق polls التي ناقشناها في هذا المقال.
</p>

<p>
	إن ما تعلمته هنا يمهد لك الطريق لتجهيز بيئة متعددة الحاويات فيمكنك جمع حاوية Django/Gunicorn التي عملنا عليها مع حاوية تتضمن خادم وكيل عكسي Nginx لمعالجة طلبات HTTP بكفاءة، والحصول على شهادات <abbr title="Transport Layer Security | بروتوكول أمن طبقة النقل">TLS</abbr> لتشفير هذه الطلبات عبر حاوية ثالثة تتضمن <a href="https://certbot.eff.org/" rel="external nofollow">Certbot</a>، وفي النهاية إدارة هذه الحاويات جميعًا من خلال <a href="https://docs.docker.com/compose/" rel="external nofollow">Docker Compose</a>.
</p>

<p>
	وقبل الختام لابد لنا من التنويه إلى أن متطلبات العمل في بيئة حقيقية تفرض عليك تشغيل Gunicorn خلف خادم HTTP وإلا فإن تطبيقك سيكون عرضة لهجمات الحرمان من الخدمة، كذلك عليك زيادة عدد العمال عن الذي اعتمدنا في المقال فالعدد 3 بالتأكيد لن يكون مناسبًا، عدّل العدد ليلائم حجم الطلب على الموقع، كذلك الأمر بالنسبة لتفريغ مكونات التطبيق الساكنة على وحدة تخزين كائني خارجية فقد اخترنا ذلك للتخفيف من عبء إدارتها في بيئة متعددة الحاويات أما في بيئتك الفعلية فاختر التصميم الذي تجده مناسبًا لمتطلباتك.
</p>

<p>
	أخيرًا يمكنك تسجيل الحاوية التي أعددتها على <a href="https://hub.docker.com/" rel="external nofollow">Dockerhub</a> وجعلها متوفرة لأي نظام يحتوي دوكر سواء أكان خوادم أوبونتو أو بيئات افتراضية أو نظم حاويات عنقودية مثل Kubernetes.
</p>

<p>
	ترجمة -وبتصرف- للمقال <a href="https://www.digitalocean.com/community/tutorials/how-to-build-a-django-and-gunicorn-application-with-docker" rel="external nofollow">How to Build a Django and Gunicorn Application with Docker</a> لصاحبيه Hanif Jetha و Justin Ellingwood.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D9%85%D8%A7-%D9%87%D9%8A-%D8%AA%D9%82%D9%86%D9%8A%D8%A9-docker%D8%9F-r639/" rel="">ما هي تقنية Docker؟</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/servers/databases/%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-r602/" rel="">التعامل مع قواعد البيانات</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/%D9%86%D8%B8%D8%A7%D9%85-%D9%83%D9%88%D8%A8%D9%8A%D8%B1%D9%86%D8%AA%D8%B3-kubernetes-%D9%88%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%B9%D9%85%D9%84%D9%87-r598/" rel="">نظام كوبيرنتس Kubernetes وكيفية عمله</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/linux/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%B5%D8%AF%D9%81%D8%A9-%D8%A8%D8%A7%D8%B4-bash-r606/" rel="">مدخل إلى صدفة باش Bash</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">658</guid><pubDate>Sun, 06 Nov 2022 06:30:56 +0000</pubDate></item><item><title>&#x62A;&#x62B;&#x628;&#x64A;&#x62A; &#x627;&#x644;&#x623;&#x62F;&#x627;&#x629; &#x62F;&#x648;&#x643;&#x631; &#x643;&#x648;&#x645;&#x628;&#x648;&#x632; Docker Compose &#x648;&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645;&#x647;&#x627; &#x636;&#x645;&#x646; &#x646;&#x638;&#x627;&#x645; &#x644;&#x64A;&#x646;&#x643;&#x633; &#x623;&#x648;&#x628;&#x648;&#x646;&#x62A;&#x648;</title><link>https://academy.hsoub.com/devops/cloud-computing/docker/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D8%A7%D9%84%D8%A3%D8%AF%D8%A7%D8%A9-%D8%AF%D9%88%D9%83%D8%B1-%D9%83%D9%88%D9%85%D8%A8%D9%88%D8%B2-docker-compose-%D9%88%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85%D9%87%D8%A7-%D8%B6%D9%85%D9%86-%D9%86%D8%B8%D8%A7%D9%85-%D9%84%D9%8A%D9%86%D9%83%D8%B3-%D8%A3%D9%88%D8%A8%D9%88%D9%86%D8%AA%D9%88-r654/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_10/63401398278a2_----Docker-Compose-----.jpg.726d7888cb4a3425d77b8f98321c3e65.jpg" /></p>

<p>
	تُبسّط أداة <a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%AF%D9%88%D9%83%D8%B1-docker-r607/" rel="">دوكر Docker</a> إدارة إجراءات التطبيق في الحاويات؛ إذ <a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D9%85%D8%A7-%D8%B9%D9%84%D9%8A%D9%83-%D9%85%D8%B9%D8%B1%D9%81%D8%AA%D9%87-%D8%B9%D9%86-%D8%A7%D9%84%D9%81%D8%B1%D9%82-%D8%A8%D9%8A%D9%86-%D8%AF%D9%88%D9%83%D8%B1-%D9%88%D8%A7%D9%84%D8%A3%D8%AC%D9%87%D8%B2%D8%A9-%D8%A7%D9%84%D8%A7%D9%81%D8%AA%D8%B1%D8%A7%D8%B6%D9%8A%D8%A9-r456/" rel="">تشبه الحاويات الأجهزة الافتراضية Virtual Machines</a> بالكثير من الجوانب، إلا أنها تتميز بخفة الوزن كما أنها أفضل من ناحية استخدام الموارد. يتيح ذلك للمطورين تجزئة بيئة التطبيق إلى خدمات معزولة متعددة.
</p>

<p>
	يصبح تنظيم جميع الحاويات التي تعود للخدمات المختلفة التي يعتمد تطبيق ما عليها أمرًا صعبًا، إذ أنّ تشغيلها معًا والاتصال فيما بينها وحتى إيقافها عن العمل يدويًا ليس عمليًا. تسمح أداة دوكر كومبوز <a href="https://docs.docker.com/compose/" rel="external nofollow">Docker Compose</a> بتشغيل بيئات التطبيق متعددة الحاويات اعتمادًا على مجموعة تعريفات مُخزّنة في ملف YAML، كما تبني هذه الأداة بيئات كاملة قابلة للتخصيص حسب الحاجة متضمنة العديد من الحاويات التي تتشارك الشبكات وأقراص التخزين فيما بينها.
</p>

<p>
	نوضح فيما يلي طريقة تثبيت الأداة دوكر كومبوز على خادم ذي <a href="https://academy.hsoub.com/devops/linux/%D9%85%D8%A7-%D9%87%D9%88-%D9%86%D8%B8%D8%A7%D9%85-%D8%A7%D9%84%D8%AA%D8%B4%D8%BA%D9%8A%D9%84-%D9%84%D9%8A%D9%86%D9%83%D8%B3%D8%9F-r451/" rel="">نظام تشغيل لينكس</a> أوبونتو Ubuntu إصدار 20.04 وكيفية استخدام هذه الأداة.
</p>

<h2>
	المتطلبات الأساسية
</h2>

<p>
	يجب توفير ما يلي:
</p>

<ul>
<li>
		الوصول إلى حاسب يعمل بنظام التشغيل أوبنتو (استعملنا الإصدار 20.04 في هذا المقال) أو إلى خادم تطوير يعمل بنفس نظام التشغيل المذكور بواسطة مستخدم ذي صلاحيات إدارة sudo، ولا يُفضّل استخدام حساب الجذر Root لأسباب تتعلق بأمان <a href="https://academy.hsoub.com/devops/servers/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%AE%D8%A7%D8%AF%D9%85-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-r574/" rel="">الخادم</a>، كما يُفضل وجود جدار حماية نشط في حال العمل على خادم بعيد.
	</li>
	<li>
		تثبيت أداة دوكر على الخادم أو الجهاز المحلي.
	</li>
</ul>
<p>
	<strong>ملاحظة:</strong> نعمل على تثبيت Docker Compose v1، والذي يستخدم "docker-compose"، ولكن بدءًا من Docker Compose v2، انتقل Docker نحو استخدام أمر الإضافة "compose" كما هو موثّق في أحدث إصدار Ubuntu 22.04، بعيدًا عن "docker-compose" الأصلي. وعلى الرغم من اختلاف طريقة التثبيت إلا أن الاستخدام الفعلي قد لا يتعدى لدى كثيرٍ من المستخدمين التخلص من الرمز "-" بين الكلمتين ليتحول الاستدعاء "docker-compose" إلى "docker compose". نجد ضمن <a href="https://docs.docker.com/compose/cli-command-compatibility/" rel="external nofollow">وثائق Docker الرسمية</a> مزيدًا من المعلومات حول توافق الأوامر بين "compose" الجديد و "Docker-compose" القديم.
</p>

<h2>
	الخطوة 1 - تثبيت الأداة دوكر كومبوز
</h2>

<p>
	نرغب بتثبيت أحدث إصدار مستقر من الأداة دوكر كومبوز، لذلك سنستخدم <a href="https://github.com/docker/compose" rel="external nofollow">المستودع الرسمي GitHub </a> من أجل ذلك.
</p>

<p>
	نبدأ بالتحقق من توفر أحدث إصدار ضمن صفحة <a href="https://github.com/docker/compose/releases" rel="external nofollow">الإصدارات الخاصة بهم</a>. تجدر الإشارة إلى أن أحدث إصدار مستقر في وقت كتابة هذا المقال هو "1.29.2".
</p>

<p>
	نبدأ تحميل الإصدار "1.29.2" وحفظ الملف القابل للتنفيذ في "/usr/local/bin/docker-compose/"، مما سيجعل هذا البرنامج متاحًا للاستخدام عند الحاجة باستدعاء الأمر <code>docker-compose</code>.
</p>

<p>
	ننفذ الأمر التالي لتحقيق ذلك:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2073_8" style="">
<span class="pln">$ sudo curl </span><span class="pun">-</span><span class="pln">L </span><span class="str">"https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)"</span><span class="pln"> </span><span class="pun">-</span><span class="pln">o </span><span class="pun">/</span><span class="pln">usr</span><span class="pun">/</span><span class="pln">local</span><span class="pun">/</span><span class="pln">bin</span><span class="pun">/</span><span class="pln">docker</span><span class="pun">-</span><span class="pln">compose</span></pre>

<p>
	نعرّف الأذونات الصحيحة ليصبح الأمر <code>docker-compose</code> قابلاً للتنفيذ كما يلي:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_2073_10" style="">
<span class="pln">$ sudo chmod </span><span class="pun">+</span><span class="pln">x </span><span class="pun">/</span><span class="pln">usr</span><span class="pun">/</span><span class="kwd">local</span><span class="pun">/</span><span class="pln">bin</span><span class="pun">/</span><span class="pln">docker</span><span class="pun">-</span><span class="pln">compose</span></pre>

<p>
	نتحقق من نجاح عملية التثبيت، بتنفيذ الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_2073_12" style="">
<span class="pln">$ docker</span><span class="pun">-</span><span class="pln">compose </span><span class="pun">--</span><span class="pln">version</span></pre>

<p>
	يظهر على الشاشة خرجٌ مشابهٌ لما يلي:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_6337_9" style="">
<span class="pln">docker</span><span class="pun">-</span><span class="pln">compose version </span><span class="lit">1.29</span><span class="pun">.</span><span class="lit">2</span><span class="pun">,</span><span class="pln"> build </span><span class="lit">5becea4c</span></pre>

<p>
	انتهت عند هذه المرحلة عملية تثبيت الأداة دوكر كومبوز بنجاح. نبدأ الآن بإعداد ملف "docker-compose.yml" للحصول على بيئة محتواة وتشغيلها باستخدام هذه الأداة.
</p>

<h2>
	الخطوة 2 - إعداد ملف docker-compose.yml
</h2>

<p>
	نستطيع إنشاء أية بيئة نرغب بها عبر إعداد ملف "docker-compose.yml" ولتوضيح كيفية إعداد هذا الملف والعمل مع دوكر كومبوز، سننشئ بيئة خادم ويب باستخدام <a href="https://hub.docker.com/_/nginx" rel="external nofollow">صورة Nginx الرسمية</a> من<a href="https://hub.docker.com" rel="external nofollow"> Docker-Hub </a>، الذي يعرّف بأنه سجل دوكر العام. ستخدّم هذه البيئة المحتواة ملف <a href="https://academy.hsoub.com/programming/html/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D9%84%D8%BA%D8%A9-html-r1687/" rel="">HTML</a> واحد ساكن static، أي لا يتضمن محتويات متغيرة.
</p>

<p>
	أنشئ مجلّدًا جديدًا في مجلدك الافتراضي، ثم انتقل إليه:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_6337_23" style="">
<span class="pln">$ mkdir ~/compose-demo
$ cd ~/compose-demo</span></pre>

<p>
	ثم اضبط مجلد التطبيق ليكون المجلد الجذر لبيئة خادم Nginx على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_6337_11" style="">
<span class="pln">$ mkdir app</span></pre>

<p>
	أنشئ الملف "index.html" باستخدام محرر النصوص المفضل عن طريق إنشاء ملف جديد داخل مجلد "app" على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_6337_17" style="">
<span class="pln">$ nano app/index.html</span></pre>

<p>
	نضيف المحتوى التالي إلى هذا الملف:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_6337_15" style="">
<span class="dec">&lt;!doctype html&gt;</span><span class="pln">
</span><span class="tag">&lt;html</span><span class="pln"> </span><span class="atn">lang</span><span class="pun">=</span><span class="atv">"en"</span><span class="tag">&gt;</span><span class="pln">
</span><span class="tag">&lt;head&gt;</span><span class="pln">
    </span><span class="tag">&lt;meta</span><span class="pln"> </span><span class="atn">charset</span><span class="pun">=</span><span class="atv">"utf-8"</span><span class="tag">&gt;</span><span class="pln">
    </span><span class="tag">&lt;title&gt;</span><span class="pln">Docker Compose Demo</span><span class="tag">&lt;/title&gt;</span><span class="pln">
    </span><span class="tag">&lt;link</span><span class="pln"> </span><span class="atn">rel</span><span class="pun">=</span><span class="atv">"stylesheet"</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"https://cdn.jsdelivr.net/gh/kognise/water.css@latest/dist/dark.min.css"</span><span class="tag">&gt;</span><span class="pln">
</span><span class="tag">&lt;/head&gt;</span><span class="pln">
</span><span class="tag">&lt;body&gt;</span><span class="pln">

    </span><span class="tag">&lt;h1&gt;</span><span class="pln">This is a Docker Compose Demo Page.</span><span class="tag">&lt;/h1&gt;</span><span class="pln">
    </span><span class="tag">&lt;p&gt;</span><span class="pln">This content is being served by an Nginx container.</span><span class="tag">&lt;/p&gt;</span><span class="pln">

</span><span class="tag">&lt;/body&gt;</span><span class="pln">
</span><span class="tag">&lt;/html&gt;</span></pre>

<p>
	نحفظ ونغلق الملف عند الانتهاء ويكون ذلك عند استخدام محرر النصوص نانو nano، بالضغط على الاختصار "CTRL + X"، ثم الزر "Y" ثم زر الإدخال "ENTER" للتأكيد.
</p>

<p>
	أنشئ بعد ذلك الملف "docker-compose.yml" بتنفيذ الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_6337_26" style="">
<span class="pln">$ nano docker-compose.yml</span></pre>

<p>
	أضف المحتوى التالي إلى ملف "docker-compose.yml":
</p>

<pre class="ipsCode">
version: '3.7'
services:
  web:
    image: nginx:alpine
    ports:
      - "8000:80"
    volumes:
      - ./app:/usr/share/nginx/html
</pre>

<p>
	يبدأ ملف "docker-compose.yml" عادةً بتحديد رقم الإصدار "version". إذ يُعلم هذا الرقم الأداة دوكر كومبوز أي إصدار من الإعدادات سيُستخدم.
</p>

<p>
	نجد بعد رقم الإصدار جزء الخدمات "services"، إذ تُعد هنا الخدمات التي تشكّل هذه البيئة. يتضمن تطبيقنا الحالي خدمةً واحدةً فقط نسميها "web"، وتستخدم هذه الخدمة صورة "nginx: alpine" وتضبط إعادة توجيه المنفذ باستخدام الموجّه "ports"، ويعني ذلك إعادة توجيه جميع الطلبات الواردة إلى المنفذ "8000" من الجهاز المضيف (النظام الذي يُشغِّل أداة <a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-docker-compose-%D8%B9%D9%84%D9%89-%D8%AF%D8%A8%D9%8A%D8%A7%D9%86-r464/" rel="">دوكر كومبوز</a>) إلى حاوية "web" عبر المنفذ "80" حيث يعمل خادم Nginx.
</p>

<p>
	ينشئ الموجّه "volumes" <a href="https://docs.docker.com/compose/compose-file/#volumes" rel="external nofollow">وحدة تخزين مشتركة</a> بين الجهاز المضيف والحاوية. نختار مشاركة المجلد "app" الموجود محليًا مع الحاوية، إذ أنّ مسار تخزين المجلد داخل الحاوية هو "usr/share/nginx/html/" والذي يصبح الصفحة الافتراضية لخادم Nginx.
</p>

<p>
	احفظ الملف واغلقه.
</p>

<p>
	انتهى إعداد صفحة توضيحية وملف "docker-compose.yml" لإنشاء بيئة خادم ويب محتواة. نحتاج لاختبار هذه البيئة وتشغيلها باستخدام أداة دوكر كومبوز.
</p>

<h2>
	الخطوة 3 - تشغيل أداة دوكر كومبوز
</h2>

<p>
	نستدعي أداة دوكر كومبوز بعد الانتهاء من إعداد الملف "docker-compose.yml"، وعندها يمكن إرسال الطلب إلى الخادم. يبدأ الأمر بتحميل صور دوكر الضرورية وإنشاء حاوية لخدمة "web"، وفي النهاية تشغيل البيئة الحاوية بالنمط المخفي، وذلك بتنفيذ الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_6337_28" style="">
<span class="pln">$ docker</span><span class="pun">-</span><span class="pln">compose up </span><span class="pun">–</span><span class="pln">d</span></pre>

<p>
	تبحث أداة دوكر كومبوز أولاً عن النسخة المحددة ضمن حاسبك المحلي، وإذا لم يعثر عليها، يُحمّل الصورة من مخزن Docker Hub ويحتفظ بها في حال الحاجة لها لاحقًا، يظهر الخرج على نحوٍ مشابهٍ لما يلي:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_6337_30" style="">
<span class="typ">Creating</span><span class="pln"> network </span><span class="str">"compose-demo_default"</span><span class="pln"> </span><span class="kwd">with</span><span class="pln"> the </span><span class="kwd">default</span><span class="pln"> driver
</span><span class="typ">Pulling</span><span class="pln"> web </span><span class="pun">(</span><span class="pln">nginx</span><span class="pun">:</span><span class="pln">alpine</span><span class="pun">)...</span><span class="pln"> 
alpine</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Pulling</span><span class="pln"> </span><span class="kwd">from</span><span class="pln"> library</span><span class="pun">/</span><span class="pln">nginx 
cbdbe7a5bc2a</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Pull</span><span class="pln"> complete
 </span><span class="lit">10c113fb0c77</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Pull</span><span class="pln"> complete 
</span><span class="lit">9ba64393807b</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Pull</span><span class="pln"> complete
 c829a9c40ab2</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Pull</span><span class="pln"> complete 
</span><span class="lit">61d685417b2f</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Pull</span><span class="pln"> complete 
</span><span class="typ">Digest</span><span class="pun">:</span><span class="pln"> sha256</span><span class="pun">:</span><span class="lit">57254039c6313fe8c53f1acbf15657ec9616a813397b74b063e32443427c5502</span><span class="pln">
 </span><span class="typ">Status</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Downloaded</span><span class="pln"> newer image </span><span class="kwd">for</span><span class="pln"> nginx</span><span class="pun">:</span><span class="pln">alpine 
</span><span class="typ">Creating</span><span class="pln"> compose</span><span class="pun">-</span><span class="pln">demo_web_1 </span><span class="pun">...</span><span class="pln"> </span><span class="kwd">done</span></pre>

<p>
	<strong>ملاحظة:</strong> في حال طباعة ما يدل على أنه توجد مشكلة في الأذونات، فهذا يعني أنّ أداة دوكر غير قادرة على العمل دون صلاحية sudo ويجب حل هذه المشكلة أولًا قبل متابعة العمل.
</p>

<p>
	أصبحت البيئة جاهزة وتعمل في الخلفية، نتحقق من أن الحاوية نشطة عبر تنفيذ الأمر:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_6337_32" style="">
<span class="pln">$ docker</span><span class="pun">-</span><span class="pln">compose ps</span></pre>

<p>
	تظهر معلومات حول الحاويات قيد التشغيل وحالتها، إضافةً إلى عمليات إعادة توجيه المنافذ المستخدمة حاليًا:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_6337_34" style="">
<span class="pln">      </span><span class="typ">Name</span><span class="pln">                     </span><span class="typ">Command</span><span class="pln">               </span><span class="typ">State</span><span class="pln">          </span><span class="typ">Ports</span><span class="pln">        
</span><span class="pun">----------------------------------------------------------------------------------</span><span class="pln">
compose</span><span class="pun">-</span><span class="pln">demo_web_1   </span><span class="pun">/</span><span class="pln">docker</span><span class="pun">-</span><span class="pln">entrypoint</span><span class="pun">.</span><span class="pln">sh ngin </span><span class="pun">...</span><span class="pln">   </span><span class="typ">Up</span><span class="pln">      </span><span class="lit">0.0</span><span class="pun">.</span><span class="lit">0.0</span><span class="pun">:</span><span class="lit">8000</span><span class="pun">-&gt;</span><span class="lit">80</span><span class="pun">/</span><span class="pln">tcp</span></pre>

<p>
	نستطيع الوصول إلى التطبيق التوضيحي من خلال طلب الرابط "localhost:8000" ضمن متصفح الويب في حال كان تشغيل العرض التوضيحي على الجهاز المحلي، أو "your<em>server</em>domain<em>or</em>IP: 8000" في حال كان تشغيل العرض التوضيحي على خادم بعيد.
</p>

<p>
	يظهر ضمن المتصفح محتوى كما يلي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="109260" href="https://academy.hsoub.com/uploads/monthly_2022_10/demo_page.png.9fe9d0b0412060c9757970a8f085f9be.png" rel=""><img alt="demo_page.png" class="ipsImage ipsImage_thumbnailed" data-fileid="109260" data-unique="tsj0yipsk" src="https://academy.hsoub.com/uploads/monthly_2022_10/demo_page.png.9fe9d0b0412060c9757970a8f085f9be.png" style="width: 550px; height: auto;"></a>
</p>

<p>
	تحافظ وحدة التخزين المشتركة التي اعددناها داخل ملف "docker-compose.yml" على مزامنة ملفات مجلد "app" الموجود محليًا مع المجلّد الموجود ضمن الحاوية، أي أنّ أية تغييرات على ملف "index.html" ستنعكس تلقائيًا على المتصفح عند إعادة تحميل الصفحة.
</p>

<h2>
	الخطوة 4 - التعرف على أوامر الأداة دوكر كومبوز
</h2>

<p>
	تستخدم أوامر الأداة دوكر كومبوز لإدارة البيئة المحتوية والتفاعل معها. نتحقق من السجلات المُتشأة عن عمل حاوية خادم Nginx، باستخدام الأمر <code>logs</code>:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_6337_38" style="">
<span class="pln">$ docker</span><span class="pun">-</span><span class="pln">compose logs</span></pre>

<p>
	يظهر محتوًى مشابه لما يلي:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_6337_40" style="">
<span class="typ">Attaching</span><span class="pln"> to compose</span><span class="pun">-</span><span class="pln">demo_web_1
web_1  </span><span class="pun">|</span><span class="pln"> </span><span class="str">/docker-entrypoint.sh: /</span><span class="pln">docker</span><span class="pun">-</span><span class="pln">entrypoint</span><span class="pun">.</span><span class="pln">d</span><span class="pun">/</span><span class="pln"> </span><span class="kwd">is</span><span class="pln"> </span><span class="kwd">not</span><span class="pln"> empty</span><span class="pun">,</span><span class="pln"> will attempt to perform configuration
web_1  </span><span class="pun">|</span><span class="pln"> </span><span class="str">/docker-entrypoint.sh: Looking for shell scripts in /</span><span class="pln">docker</span><span class="pun">-</span><span class="pln">entrypoint</span><span class="pun">.</span><span class="pln">d</span><span class="pun">/</span><span class="pln">
web_1  </span><span class="pun">|</span><span class="pln"> </span><span class="str">/docker-entrypoint.sh: Launching /</span><span class="pln">docker</span><span class="pun">-</span><span class="pln">entrypoint</span><span class="pun">.</span><span class="pln">d</span><span class="pun">/</span><span class="lit">10</span><span class="pun">-</span><span class="pln">listen</span><span class="pun">-</span><span class="pln">on</span><span class="pun">-</span><span class="pln">ipv6</span><span class="pun">-</span><span class="kwd">by</span><span class="pun">-</span><span class="kwd">default</span><span class="pun">.</span><span class="pln">sh
web_1  </span><span class="pun">|</span><span class="pln"> </span><span class="lit">10</span><span class="pun">-</span><span class="pln">listen</span><span class="pun">-</span><span class="pln">on</span><span class="pun">-</span><span class="pln">ipv6</span><span class="pun">-</span><span class="kwd">by</span><span class="pun">-</span><span class="kwd">default</span><span class="pun">.</span><span class="pln">sh</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Getting</span><span class="pln"> the checksum of </span><span class="pun">/</span><span class="pln">etc</span><span class="pun">/</span><span class="pln">nginx</span><span class="pun">/</span><span class="pln">conf</span><span class="pun">.</span><span class="pln">d</span><span class="pun">/</span><span class="kwd">default</span><span class="pun">.</span><span class="pln">conf
web_1  </span><span class="pun">|</span><span class="pln"> </span><span class="lit">10</span><span class="pun">-</span><span class="pln">listen</span><span class="pun">-</span><span class="pln">on</span><span class="pun">-</span><span class="pln">ipv6</span><span class="pun">-</span><span class="kwd">by</span><span class="pun">-</span><span class="kwd">default</span><span class="pun">.</span><span class="pln">sh</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Enabled</span><span class="pln"> listen on </span><span class="typ">IPv6</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> </span><span class="pun">/</span><span class="pln">etc</span><span class="pun">/</span><span class="pln">nginx</span><span class="pun">/</span><span class="pln">conf</span><span class="pun">.</span><span class="pln">d</span><span class="pun">/</span><span class="kwd">default</span><span class="pun">.</span><span class="pln">conf
web_1  </span><span class="pun">|</span><span class="pln"> </span><span class="str">/docker-entrypoint.sh: Launching /</span><span class="pln">docker</span><span class="pun">-</span><span class="pln">entrypoint</span><span class="pun">.</span><span class="pln">d</span><span class="pun">/</span><span class="lit">20</span><span class="pun">-</span><span class="pln">envsubst</span><span class="pun">-</span><span class="pln">on</span><span class="pun">-</span><span class="pln">templates</span><span class="pun">.</span><span class="pln">sh
web_1  </span><span class="pun">|</span><span class="pln"> </span><span class="pun">/</span><span class="pln">docker</span><span class="pun">-</span><span class="pln">entrypoint</span><span class="pun">.</span><span class="pln">sh</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Configuration</span><span class="pln"> complete</span><span class="pun">;</span><span class="pln"> ready </span><span class="kwd">for</span><span class="pln"> start up
web_1  </span><span class="pun">|</span><span class="pln"> </span><span class="lit">172.22</span><span class="pun">.</span><span class="lit">0.1</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">[</span><span class="lit">02</span><span class="pun">/</span><span class="typ">Jun</span><span class="pun">/</span><span class="lit">2020</span><span class="pun">:</span><span class="lit">10</span><span class="pun">:</span><span class="lit">47</span><span class="pun">:</span><span class="lit">13</span><span class="pln"> </span><span class="pun">+</span><span class="lit">0000</span><span class="pun">]</span><span class="pln"> </span><span class="str">"GET / HTTP/1.1"</span><span class="pln"> </span><span class="lit">200</span><span class="pln"> </span><span class="lit">353</span><span class="pln"> </span><span class="str">"-"</span><span class="pln"> </span><span class="str">"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"</span><span class="pln"> </span><span class="str">"-"</span></pre>

<p>
	نستطيع إيقاف تنفيذ البيئة مؤقتًا دون تغيير الحالة الراهنة للحاويات بتنفيذ الأمر:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_6337_42" style="">
<span class="pln">$ docker</span><span class="pun">-</span><span class="pln">compose pause</span></pre>

<p>
	ليكون الخرج على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_6337_44" style="">
<span class="typ">Pausing</span><span class="pln"> compose</span><span class="pun">-</span><span class="pln">demo_web_1 </span><span class="pun">...</span><span class="pln"> </span><span class="kwd">done</span></pre>

<p>
	نستأنف تنفيذ البيئة بتنفيذ الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_6337_46" style="">
<span class="pln">$ docker</span><span class="pun">-</span><span class="pln">compose unpause</span></pre>

<p>
	ويكون الخرج على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_6337_48" style="">
<span class="pln"> </span><span class="typ">Unpausing</span><span class="pln"> compose</span><span class="pun">-</span><span class="pln">demo_web_1 </span><span class="pun">...</span><span class="pln"> </span><span class="kwd">done</span></pre>

<p>
	ينهي الأمر <code>stop</code> تنفيذ الحاوية، لكنه لن يزيل البيانات المرتبطة بالحاويات:
</p>

<pre class="ipsCode">
$ docker-compose stop
</pre>

<p>
	ويكون الخرج على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_6337_51" style="">
<span class="pln"> </span><span class="typ">Stopping</span><span class="pln"> compose</span><span class="pun">-</span><span class="pln">demo_web_1 </span><span class="pun">...</span><span class="pln"> </span><span class="kwd">done</span></pre>

<p>
	نستخدم الأمر <code>down</code> من أجل إزالة الحاويات والشبكات ووحدات التخزين المرتبطة بالبيئة الحاوية بتنفيذ الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_6337_53" style="">
<span class="pln">$ docker</span><span class="pun">-</span><span class="pln">compose down</span></pre>

<p>
	يظهر الخرج على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_6337_55" style="">
<span class="typ">Removing</span><span class="pln"> compose</span><span class="pun">-</span><span class="pln">demo_web_1 </span><span class="pun">...</span><span class="pln"> </span><span class="kwd">done</span><span class="pln">
</span><span class="typ">Removing</span><span class="pln"> network compose</span><span class="pun">-</span><span class="pln">demo_default</span></pre>

<p>
	لن يزيل تنفيذ هذا الأمر الصورة الأساسية التي تستخدمها أداة دوكر كومبوز لإنشاء البيئة وهي "nginx:alpine"، وعند تطبيق الأمر <code>docker-compose up</code> فتُنجز العملية بسرعة لأن هذه الصورة موجودة على الجهاز المحلي ولا داعي لتحميلها من الموقع الرسمي مجددًا.
</p>

<p>
	نزيل الصورة الأساسية عند الحاجة إلى ذلك بتنفيذ الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_6337_57" style="">
<span class="pln">$ docker image rm nginx</span><span class="pun">:</span><span class="pln">alpine</span></pre>

<p>
	وتكون المخرجات على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_6337_59" style="">
<span class="typ">Untagged</span><span class="pun">:</span><span class="pln"> nginx</span><span class="pun">:</span><span class="pln">alpine 
</span><span class="typ">Untagged</span><span class="pun">:</span><span class="pln"> nginx@sha256</span><span class="pun">:</span><span class="pln">b89a6ccbda39576ad23fd079978c967cecc6b170db6e7ff8a769bf2259a71912
</span><span class="typ">Deleted</span><span class="pun">:</span><span class="pln"> sha256</span><span class="pun">:</span><span class="lit">7d0cdcc60a96a5124763fddf5d534d058ad7d0d8d4c3b8be2aefedf4267d0270</span><span class="pln"> 
</span><span class="typ">Deleted</span><span class="pun">:</span><span class="pln"> sha256</span><span class="pun">:</span><span class="lit">05a0eaca15d731e0029a7604ef54f0dda3b736d4e987e6ac87b91ac7aac03ab1</span><span class="pln"> 
</span><span class="typ">Deleted</span><span class="pun">:</span><span class="pln"> sha256</span><span class="pun">:</span><span class="pln">c6bbc4bdac396583641cb44cd35126b2c195be8fe1ac5e6c577c14752bbe9157 
</span><span class="typ">Deleted</span><span class="pun">:</span><span class="pln"> sha256</span><span class="pun">:</span><span class="lit">35789b1e1a362b0da8392ca7d5759ef08b9a6b7141cc1521570f984dc7905eb6</span><span class="pln">
</span><span class="typ">Deleted</span><span class="pun">:</span><span class="pln"> sha256</span><span class="pun">:</span><span class="pln">a3efaa65ec344c882fe5d543a392a54c4ceacd1efd91662d06964211b1be4c08 
</span><span class="typ">Deleted</span><span class="pun">:</span><span class="pln"> sha256</span><span class="pun">:</span><span class="lit">3e207b409db364b595ba862cdc12be96dcdad8e36c59a03b7b3b61c946a5741a</span></pre>

<h2>
	الخاتمة
</h2>

<p>
	تضمن هذا المقال خطوات تثبيت الأداة دوكر كومبوز وإعداد بيئة حاوية بالاعتماد على صورة <a href="https://academy.hsoub.com/devops/servers/web/nginx/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-nginx-%D8%B9%D9%84%D9%89-%D8%A3%D9%88%D8%A8%D9%88%D9%86%D8%AA%D9%88-1804-r434/" rel="">خادم الويب Nginx</a>، كما تضمن بعض الأوامر الأساسية لإدارة هذه البيئة.
</p>

<p>
	ترجمة -وبتصرف- للمقال <a href="https://www.digitalocean.com/community/tutorials/how-to-install-and-use-docker-compose-on-ubuntu-20-04" rel="external nofollow">How To Install and Use Docker Compose on Ubuntu 20.04</a> لصاحبيه Tony Tran و Erika Heidi.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%AD%D8%A7%D9%88%D9%8A%D8%A9-%D9%84%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D9%84%D8%A7%D8%B1%D8%A7%D9%81%D9%8A%D9%84-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%AF%D9%88%D9%83%D8%B1-%D9%83%D9%88%D9%85%D8%A8%D9%88%D8%B2-docker-compose-r652/" rel="">إعداد حاوية لتطبيق لارافيل باستخدام دوكر كومبوز Docker Compose</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D9%84%D8%A7%D8%B1%D8%A7%D9%81%D9%8A%D9%84-laravel-%D8%B9%D9%84%D9%89-%D8%AF%D9%88%D9%83%D8%B1-%D9%83%D9%88%D9%85%D8%A8%D9%88%D8%B2-docker-compose-r650/" rel="">تثبيت وإعداد لارافيل Laravel على دوكر كومبوز Docker Compose</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-docker-compose-%D8%B9%D9%84%D9%89-%D8%AF%D8%A8%D9%8A%D8%A7%D9%86-r464/" rel="">كيفية تثبيت Docker Compose على دبيان</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/nodejs/%D9%86%D9%82%D9%84-%D8%B3%D9%8A%D8%B1-%D8%B9%D9%85%D9%84-docker-compose-%D8%A5%D9%84%D9%89-kubernetes-r812/" rel="">نقل سير عمل Docker Compose إلى Kubernetes</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D9%84%D8%A7%D8%B1%D8%A7%D9%81%D9%8A%D9%84-%D9%85%D8%B9-%D8%AE%D8%A7%D8%AF%D9%85-nginx-%D9%88%D9%82%D8%A7%D8%B9%D8%AF%D8%A9-%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-mysql-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%AF%D9%88%D9%83%D8%B1-%D9%83%D9%88%D9%85%D8%A8%D9%88%D8%B2-docker-compose-r651/" rel="">إعداد لارافيل مع خادم Nginx وقاعدة بيانات MySQL باستخدام دوكر كومبوز Docker Compose</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">654</guid><pubDate>Sat, 08 Oct 2022 04:17:09 +0000</pubDate></item><item><title>&#x625;&#x639;&#x62F;&#x627;&#x62F; &#x62D;&#x627;&#x648;&#x64A;&#x629; &#x644;&#x62A;&#x637;&#x628;&#x64A;&#x642; &#x644;&#x627;&#x631;&#x627;&#x641;&#x64A;&#x644; &#x628;&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x62F;&#x648;&#x643;&#x631; &#x643;&#x648;&#x645;&#x628;&#x648;&#x632; Docker Compose</title><link>https://academy.hsoub.com/devops/cloud-computing/docker/%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%AD%D8%A7%D9%88%D9%8A%D8%A9-%D9%84%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D9%84%D8%A7%D8%B1%D8%A7%D9%81%D9%8A%D9%84-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%AF%D9%88%D9%83%D8%B1-%D9%83%D9%88%D9%85%D8%A8%D9%88%D8%B2-docker-compose-r652/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_10/633ff70a9de56_-------Docker-Compose---.jpg.fc6fdfa43c63900b6261d3eefa74e273.jpg" /></p>

<p>
	نعرّف الاحتواء ضمن حاوية Containerization على أنه عملية تكييف التطبيق ومكوناته لتصبح قادرة على العمل ضمن بيئة بسيطة أساسية المقومات ندعوها <a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D9%85%D8%A7-%D9%87%D9%8A-%D8%B5%D9%88%D8%B1%D8%A9-%D8%A7%D9%84%D8%AD%D8%A7%D9%88%D9%8A%D8%A9-container-image%D8%9F-r587/" rel="">الحاوية</a> لتشكّل بيئة معزولة يمكن التخلص منها بسهولة عند الرغبة بذلك، كما يمكن الاستفادة منها لتطوير واختبار ونشر التطبيقات ضمن بيئات الإنتاج.
</p>

<p>
	نعمل في هذا المقال على استخدام أداة <a href="https://docs.docker.com/compose/" rel="external nofollow">Docker Compose</a> لاحتواء تطبيق <a href="https://laravel.com" rel="external nofollow">لارافيل</a> بهدف تطويره ضمن بيئة مستقلة خاصة به. نحصل على تطبيق لارافيل توضيحي موزّع على ثلاث حاويات خدمة منفصلة كما يلي:
</p>

<ul>
<li>
		خدمة تطبيق "app" تعمل على PHP7.4-FPM.
	</li>
	<li>
		خدمة قاعدة بيانات "db" تحتوي خادم MySQL 5.7.
	</li>
	<li>
		خدمة "nginx" تستخدم خدمة "app" من أجل تحليل شيفرة برمجية <a href="https://academy.hsoub.com/programming/php/%D8%AA%D9%85%D9%87%D9%8A%D8%AF-%D8%A5%D9%84%D9%89-%D9%84%D8%BA%D8%A9-php-r235/" rel="">بلغة PHP</a> قبل تقديم تطبيق لارافيل للمستخدم النهائي.
	</li>
</ul>
<p>
	نستخدم وحدات التخزين المشتركة Shared Volumes من أجل مزامنة ملفات التطبيق وذلك لتطوير تطبيق بسيط وتصحيح الأخطاء بسهولة، كما نستخدم أوامر "docker-compose exec" لتشغيل تعليمات "Composer" و "Artisan" في حاوية التطبيق.
</p>

<h2>
	المتطلبات الأساسية
</h2>

<ul>
<li>
		الوصول إلى حاسب يعمل بنظام التشغيل أوبنتو Ubuntu (استعملنا الإصدار 18.04 في هذا المقال) أو إلى خادم تطوير يعمل بنفس نظام التشغيل المذكور بواسطة مستخدم ذي صلاحيات إدارة sudo، ولا يُفضّل استخدام حساب الجذر Root لأسباب تتعلق بأمان الخادم، كما يُفضّل وجود جدار حماية نشط في حال العمل على خادم بعيد.
	</li>
	<li>
		تثبيت الأداة دوكر على الخادم أو الجهاز المحلي.
	</li>
	<li>
		تثبيت أداة دوكر كومبوز "Docker Compose" على <a href="https://academy.hsoub.com/devops/servers/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%AE%D8%A7%D8%AF%D9%85-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-r574/" rel="">الخادم</a> أو الجهاز المحلي.
	</li>
</ul>
<h2>
	الخطوة 1- الحصول على التطبيق التوضيحي
</h2>

<p>
	نبدأ بتحميل تطبيق لارافيل التوضيحي من مخزن <a href="https://github.com/do-community/travellist-laravel-demo" rel="external nofollow">Github</a> وننتقل إلى الفرع tutorial-01 والذي يتضمن تطبيقًا بسيطًا مبنيًا باستخدام لارافيل. نعتمد في هذا العمل على الإصدار tutorial-1.0.1 والذي نحملّه ضمن المجلد الأساسي للمستخدم بتنفيذ الأمر:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_7693_13" style="">
<span class="pln">cd </span><span class="pun">~</span><span class="pln">
curl </span><span class="pun">-</span><span class="pln">L https</span><span class="pun">:</span><span class="com">//github.com/do-community/travellist-laravel-demo/archive/tutorial-1.0.1.zip -o travellist.zip</span></pre>

<p>
	يبدأ تحميل الملف المضغوط بصيغة "zip" لذلك يجب عند انتهاء التحميل فك الضغط بعد التأكد من تحديث الحزم الخاصة بنظام التشغيل ما لم تكن محدّثة مؤخرًا كما يجب تثبيت الأداة "unzip" لتحقيق ذلك. ننفذ الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_7693_11" style="">
<span class="pln">sudo apt update
sudo apt install unzip</span></pre>

<p>
	ننفذ الأمر التالي لفك الضغط وتسمية المجلد ليصبح باسم "travellist-demo" بهدف السهولة:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_7693_15" style="">
<span class="pln">unzip travellist</span><span class="pun">.</span><span class="pln">zip
mv travellist</span><span class="pun">-</span><span class="pln">laravel</span><span class="pun">-</span><span class="pln">demo</span><span class="pun">-</span><span class="pln">tutorial</span><span class="pun">-</span><span class="lit">1.0</span><span class="pun">.</span><span class="lit">1</span><span class="pln"> travellist</span><span class="pun">-</span><span class="pln">demo</span></pre>

<p>
	ننتقل إلى المجلد "travellist-demo" من خلال الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_7693_18" style="">
<span class="pln">cd travellist</span><span class="pun">-</span><span class="pln">demo</span></pre>

<h2>
	الخطوة 2- إعداد ملف الضبط الخاص بالتطبيق
</h2>

<p>
	تتواجد ملفات ضبط لارافيل في مجلد يُدعى "config" ضمن مجلد الجذر للتطبيق، إذ يُستخدم الملف "env." لعمليات الضبط المتعلقة بالبيئة، مثل بيانات الاعتماد وأية معلومات تختلف بين عمليات نشر التطبيقات لذلك لا يُضمّن هذا الملف ضمن ملفات التحكم بالإصدارات لكي لا تنتشر هذه المعلومات لجميع المستخدمين الذين يستخدمون صورة التطبيق لاحقًا.
</p>

<p>
	<strong>تحذير</strong>: يحتوي ملف تهيئة البيئة على معلومات حساسة حول الخادم، بما في ذلك بيانات اعتماد قاعدة البيانات ومفاتيح الأمان، لهذا السبب لا يجب أبدًا مشاركة هذا الملف علنًا.
</p>

<p>
	توجد أولوية للقيم الموجودة في الملف "env." على القيم الموجودة في ملفات التهيئة الأخرى الموجودة في المجلد "config".
</p>

<p>
	تتطلب كل عملية تثبيت في بيئة جديدة ملف بيئة مخصص لتعريف معلومات جديدة، مثل إعدادات الاتصال في قاعدة البيانات، وخيارات التصحيح، ورابط URL للتطبيق، إضافةً لبقية العناصر او المعلومات التي تختلف تبعًا للبيئة التي يعمل بها التطبيق. ننشئ ملف "env." جديد لضبط خيارات التهيئة لبيئة التطوير التي نُعدها. ويمكننا نسخ الملف "example.env" المتاح افتراضيًا مع أي تطبيق لارافيل بتنفيذ الأمر:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_7693_20" style="">
<span class="pln">cp </span><span class="pun">.</span><span class="pln">env</span><span class="pun">.</span><span class="pln">example </span><span class="pun">.</span><span class="pln">env</span></pre>

<p>
	نحرر الملف باستخدام محرر النصوص نانو "nano" أو باختيار محرر النصوص المفضّل:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_7693_23" style="">
<span class="pln">nano </span><span class="pun">.</span><span class="pln">env</span></pre>

<p>
	يحتوي ملف "env." الحالي من تطبيق "travellist" التوضيحي على إعدادات لاستخدام التطبيق المُحتَوى المتصل بقاعدة بيانات محلية من نوع <a href="https://academy.hsoub.com/devops/servers/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%AE%D8%A7%D8%AF%D9%85-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-r574/" rel="">MySQL</a> أي أنها متاحة على المضيف المحلي"127.0.0.1"، نحتاج لتعديل قيمة المتغير <code>DB_HOST</code> لكي يشير إلى قاعدة البيانات التي ننشئها ضمن بيئة دوكر. نعتمد في عملنا على أنّ إسم خدمة قواعد البيانات هو "db". نعدّل محتوى الملف لضبط قيم المتغيرات على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_7693_27" style="">
<span class="pln">APP_NAME</span><span class="pun">=</span><span class="typ">Travellist</span><span class="pln">
APP_ENV</span><span class="pun">=</span><span class="pln">dev
APP_KEY</span><span class="pun">=</span><span class="pln">
APP_DEBUG</span><span class="pun">=</span><span class="kwd">true</span><span class="pln">
APP_URL</span><span class="pun">=</span><span class="pln">http</span><span class="pun">:</span><span class="com">//localhost:8000</span><span class="pln">

LOG_CHANNEL</span><span class="pun">=</span><span class="pln">stack 
DB_CONNECTION</span><span class="pun">=</span><span class="pln">mysql
DB_HOST</span><span class="pun">=</span><span class="pln">db
DB_PORT</span><span class="pun">=</span><span class="lit">3306</span><span class="pln">
DB_DATABASE</span><span class="pun">=</span><span class="pln">travellist
DB_USERNAME</span><span class="pun">=</span><span class="pln">travellist_user
DB_PASSWORD</span><span class="pun">=</span><span class="pln">password
</span><span class="pun">...</span></pre>

<p>
	نستطيع تغيير القيم الخاصة ببقية المتغيرات مثل اسم قاعدة البيانات وكلمة المرور واسم المستخدم عند إعداد الملف "docker-compose.yml" من أجل تهيئة الخدمات. نضغط على الاختصار "CTRL + X" ثم الحرف "Y" وأخيرًا زر الإدخال "Enter" بعد الانتهاء من تعديل الملف.
</p>

<h2>
	الخطوة 3- إعداد Dockerfile للتطبيق
</h2>

<p>
	تستند خدمتا MySQL و Nginx إلى الصور الافتراضية التي يمكن الحصول عليها من <a href="https://hub.docker.com/" rel="external nofollow">Docker Hub</a>، ونحتاج في مثالنا إلى إنشاء صورة مخصصة لحاوية التطبيق وسننشئ ملف Dockerfile جديد لذلك.
</p>

<p>
	تعتمد صورة التطبيق <strong>travillist</strong> على <a href="https://hub.docker.com/_/php" rel="external nofollow">صورة PHP الرسمية</a> ذات الاسم "php:7.4-fpm" والمتاحة ضمن مخزن Docker Hub. نحتاج تثبيت بعض حزم PHP الإضافية باستخدام أداة إدارة الاعتمادية <a href="https://getcomposer.org/" rel="external nofollow">Composer</a>.
</p>

<p>
	ننشئ مستخدم نظام جديد لتنفيذ الأوامر المختلفة مثل أوامر "composer" و "artisan" أثناء تطوير التطبيق. يضمن إعداد "uid" امتلاك المستخدم الموجود داخل الحاوية نفس معرف المستخدم الخاص بمستخدم النظام على الجهاز المضيف أثناء تشغيل دوكر. تتزامن الملفات التي تُنشأ من الأوامر في جهاز المضيف باستخدام الأذونات الصحيحة، نستطيع استخدام أي محرر نصوص لتطوير الشيفرة البرمجية للتطبيق داخل الحاويات. ننشئ ملف Dockerfile جديد بتنفيذ الأمر:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_7693_29" style="">
<span class="pln">nano </span><span class="typ">Dockerfile</span></pre>

<p>
	ننسخ المحتوى التالي إلى ملف Dockerfile:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_7693_31" style="">
<span class="pln">FROM php</span><span class="pun">:</span><span class="lit">7.4</span><span class="pun">-</span><span class="pln">fpm

</span><span class="com"># Arguments defined in docker-compose.yml</span><span class="pln">
ARG user
ARG uid

</span><span class="com"># Install system dependencies</span><span class="pln">
RUN apt</span><span class="pun">-</span><span class="kwd">get</span><span class="pln"> update </span><span class="pun">&amp;&amp;</span><span class="pln"> apt</span><span class="pun">-</span><span class="kwd">get</span><span class="pln"> install </span><span class="pun">-</span><span class="pln">y \
    git \
    curl \
    libpng</span><span class="pun">-</span><span class="pln">dev \
    libonig</span><span class="pun">-</span><span class="pln">dev \
    libxml2</span><span class="pun">-</span><span class="pln">dev \
    zip \
    unzip

</span><span class="com"># Clear cache</span><span class="pln">
RUN apt</span><span class="pun">-</span><span class="kwd">get</span><span class="pln"> clean </span><span class="pun">&amp;&amp;</span><span class="pln"> rm </span><span class="pun">-</span><span class="pln">rf </span><span class="pun">/</span><span class="kwd">var</span><span class="pun">/</span><span class="pln">lib</span><span class="pun">/</span><span class="pln">apt</span><span class="pun">/</span><span class="pln">lists</span><span class="com">/*

# Install PHP extensions
RUN docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd

# Get latest Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer

# Create system user to run Composer and Artisan Commands
RUN useradd -G www-data,root -u $uid -d /home/$user $user
RUN mkdir -p /home/$user/.composer &amp;&amp; \
    chown -R $user:$user /home/$user

# Set working directory
WORKDIR /var/www

USER $user</span></pre>

<p>
	نحفظ الملف بعد الانتهاء من التعديل عليه. يبدأ الملف بتحديد الصورة الأساس التي نستخدمها وهي في حالة الملف السابق <code>php:7.4-fpm</code>. نثبت كومبوزر بنسخ الملف التنفيذي الخاص بـ <code>composer</code> والمتاح ضمن أحدث <a href="https://hub.docker.com/_/composer" rel="external nofollow">صورة رسمية</a> إلى صورة التطبيق الخاص بنا.
</p>

<p>
	ننشئ مستخدم نظام جديد ونبدأ بإعداد المتغيرين <code>user</code> و <code>uid</code> الذين عرّفناهما في بداية الملف Dockerfile إذ تُمرر هذه المتغيرات إلى الحاوية باستخدام الأداة دوكر كومبوز في وقت الإنشاء.
</p>

<p>
	نحدد مسار العمل الافتراضي "www/var/" ونسجل الدخول بواسطة المستخدم الذي أنشأناه مؤخرًا، للتأكد من الاتصال بقاعدة البيانات مثل مستخدم عادي وأننا في المسار الصحيح عند استخدام الأوامر مثل أوامر "composer" و "artisan" في حاوية التطبيق.
</p>

<h2>
	الخطوة 4- إعداد بنية خادم Nginx وملفات تفريغ قاعدة البيانات
</h2>

<p>
	يجب مشاركة ملفات الضبط أو التهيئة مع حاويات الخدمة عند إنشاء بيئة تطوير باستخدام الأداة دوكر كومبوز مما يسهّل إجراء التعديلات على ملفات الضبط وضبط البيئة أثناء تطوير التطبيق. ننشئ مجلد ونضع به الملفات التي تُستخدم في تهيئة وضبط حاويات الخدمة. ننشئ مجلد "docker-compose/nginx" ونضع به ملف "travellist.conf" الذي يهيئ آلية تقديم التطبيق وذلك بتنفيذ الأمر:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_7693_37" style="">
<span class="pln">mkdir </span><span class="pun">-</span><span class="pln">p docker</span><span class="pun">-</span><span class="pln">compose</span><span class="pun">/</span><span class="pln">nginx</span></pre>

<p>
	نعدل محتوى الملف "travellist.conf" باستخدام محرر النصوص المفضل عن طريق تنفيذ الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_7693_35" style="">
<span class="pln">nano docker</span><span class="pun">-</span><span class="pln">compose</span><span class="pun">/</span><span class="pln">nginx</span><span class="pun">/</span><span class="pln">travellist</span><span class="pun">.</span><span class="pln">conf</span></pre>

<p>
	نضيف الشيفرة البرمجية التالية داخله:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_7693_39" style="">
<span class="pln">server </span><span class="pun">{</span><span class="pln">
    listen </span><span class="lit">80</span><span class="pun">;</span><span class="pln">
    index index</span><span class="pun">.</span><span class="pln">php index</span><span class="pun">.</span><span class="pln">html</span><span class="pun">;</span><span class="pln">
    error_log  </span><span class="pun">/</span><span class="kwd">var</span><span class="pun">/</span><span class="pln">log</span><span class="pun">/</span><span class="pln">nginx</span><span class="pun">/</span><span class="pln">error</span><span class="pun">.</span><span class="pln">log</span><span class="pun">;</span><span class="pln">
    access_log </span><span class="pun">/</span><span class="kwd">var</span><span class="pun">/</span><span class="pln">log</span><span class="pun">/</span><span class="pln">nginx</span><span class="pun">/</span><span class="pln">access</span><span class="pun">.</span><span class="pln">log</span><span class="pun">;</span><span class="pln">
    root </span><span class="pun">/</span><span class="kwd">var</span><span class="pun">/</span><span class="pln">www</span><span class="pun">/</span><span class="kwd">public</span><span class="pun">;</span><span class="pln">
    location </span><span class="pun">~</span><span class="pln"> \.php$ </span><span class="pun">{</span><span class="pln">
        try_files $uri </span><span class="pun">=</span><span class="lit">404</span><span class="pun">;</span><span class="pln">
        fastcgi_split_path_info </span><span class="pun">^(.+</span><span class="pln">\.php</span><span class="pun">)(/.+)</span><span class="pln">$</span><span class="pun">;</span><span class="pln">
        fastcgi_pass app</span><span class="pun">:</span><span class="lit">9000</span><span class="pun">;</span><span class="pln">
        fastcgi_index index</span><span class="pun">.</span><span class="pln">php</span><span class="pun">;</span><span class="pln">
        include fastcgi_params</span><span class="pun">;</span><span class="pln">
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name</span><span class="pun">;</span><span class="pln">
        fastcgi_param PATH_INFO $fastcgi_path_info</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    location </span><span class="pun">/</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        try_files $uri $uri</span><span class="pun">/</span><span class="pln"> </span><span class="pun">/</span><span class="pln">index</span><span class="pun">.</span><span class="pln">php</span><span class="pun">?</span><span class="pln">$query_string</span><span class="pun">;</span><span class="pln">
        gzip_static on</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	يضبط الملف السابق خادم Nginx ويجبره على الاستماع للمنفذ "80" ويستخدم الصفحة <code>index.php</code> لتكون صفحة الدليل الأساسية للتطبيق. نضبط المسار الجذر Root للمستند ليصبح <code>var/www/public/</code> ونضبط Nginx ونجبره على استخدام المنفذ "9000" لمعالجة الملفات ذات اللاحقة "php.*".
</p>

<p>
	سنشارك تفريغ قاعدة البيانات database dumpfile التي تستورد عند تهيئة الحاوية لإعداد قاعدة البيانات MySQL، إذ تتوفر هذه الميزة في <a href="https://hub.docker.com/_/mysql" rel="external nofollow">MySQL 5.7</a> التي سنستخدمها في حاوية التطبيق. ننشئ مجلدًا جديدًا لتهيئة ملفات MySQL داخل المجلد "docker-compose" بتنفيذ الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_7693_43" style="">
<span class="pln">mkdir docker</span><span class="pun">-</span><span class="pln">compose</span><span class="pun">/</span><span class="pln">mysql</span></pre>

<p>
	ننشئ ملف "sql." جديد بتنفيذ الأمر:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_7693_45" style="">
<span class="pln">nano docker</span><span class="pun">-</span><span class="pln">compose</span><span class="pun">/</span><span class="pln">mysql</span><span class="pun">/</span><span class="pln">init_db</span><span class="pun">.</span><span class="pln">sql</span></pre>

<p>
	يعتمد ملف قاعدة البيانات MySQL dump التالي على قاعدة البيانات المُنشأة في مقال <a href="https://www.digitalocean.com/community/tutorials/how-to-install-and-configure-laravel-with-nginx-on-ubuntu-20-04" rel="external nofollow">لارافيل بالاعتماد على LEMP</a> من ديجيتال أوشن، إذ يُنشئ جدولًا اسمه <code>places</code> في قاعدة البيانات ثم سيملأ محتوياته بمجموعة من العينات. ولضبط بنية هذا الجدول ومحتوياته، نكتب الشيفرة البرمجية التالية في الملف "init_db.sql" ليصبح كما يلي:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_7693_47" style="">
<span class="pln">DROP TABLE IF EXISTS </span><span class="str">`places`</span><span class="pun">;</span><span class="pln">
CREATE TABLE </span><span class="str">`places`</span><span class="pln"> </span><span class="pun">(</span><span class="pln">
  </span><span class="str">`id`</span><span class="pln"> bigint</span><span class="pun">(</span><span class="lit">20</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">unsigned</span><span class="pln"> NOT NULL AUTO_INCREMENT</span><span class="pun">,</span><span class="pln">
  </span><span class="str">`name`</span><span class="pln"> varchar</span><span class="pun">(</span><span class="lit">255</span><span class="pun">)</span><span class="pln"> COLLATE utf8mb4_unicode_ci NOT NULL</span><span class="pun">,</span><span class="pln">
  </span><span class="str">`visited`</span><span class="pln"> tinyint</span><span class="pun">(</span><span class="lit">1</span><span class="pun">)</span><span class="pln"> NOT NULL DEFAULT </span><span class="str">'0'</span><span class="pun">,</span><span class="pln">
  PRIMARY KEY </span><span class="pun">(</span><span class="str">`id`</span><span class="pun">)</span><span class="pln">
</span><span class="pun">)</span><span class="pln"> ENGINE</span><span class="pun">=</span><span class="typ">InnoDB</span><span class="pln"> AUTO_INCREMENT</span><span class="pun">=</span><span class="lit">12</span><span class="pln"> DEFAULT CHARSET</span><span class="pun">=</span><span class="pln">utf8mb4 COLLATE</span><span class="pun">=</span><span class="pln">utf8mb4_unicode_ci</span><span class="pun">;</span><span class="pln">
INSERT INTO </span><span class="str">`places`</span><span class="pln"> </span><span class="pun">(</span><span class="pln">name</span><span class="pun">,</span><span class="pln"> visited</span><span class="pun">)</span><span class="pln"> VALUES </span><span class="pun">(</span><span class="str">'Berlin'</span><span class="pun">,</span><span class="lit">0</span><span class="pun">),(</span><span class="str">'Budapest'</span><span class="pun">,</span><span class="lit">0</span><span class="pun">),(</span><span class="str">'Cincinnati'</span><span class="pun">,</span><span class="lit">1</span><span class="pun">),(</span><span class="str">'Denver'</span><span class="pun">,</span><span class="lit">0</span><span class="pun">),(</span><span class="str">'Helsinki'</span><span class="pun">,</span><span class="lit">0</span><span class="pun">),(</span><span class="str">'Lisbon'</span><span class="pun">,</span><span class="lit">0</span><span class="pun">),(</span><span class="str">'Moscow'</span><span class="pun">,</span><span class="lit">1</span><span class="pun">),(</span><span class="str">'Nairobi'</span><span class="pun">,</span><span class="lit">0</span><span class="pun">),(</span><span class="str">'Oslo'</span><span class="pun">,</span><span class="lit">1</span><span class="pun">),(</span><span class="str">'Rio'</span><span class="pun">,</span><span class="lit">0</span><span class="pun">),(</span><span class="str">'Tokyo'</span><span class="pun">,</span><span class="lit">0</span><span class="pun">);</span></pre>

<p>
	يتضمن الجدول <code>places</code> ثلاثة حقول <code>id</code> و <code>name</code> و <code>visited</code> وهي على الترتيب معرّف خاص بكل مكان واسم المكان ومتغير يشير إلى زيارة المكان مسبقًا أم لا، ويمكن تعديل أسماء الحقول وإضافة حقول جديدة عند الحاجة.
</p>

<h2>
	الخطوة 5- إنشاء بيئة متعددة الحاويات باستخدام أداة دوكر كومبوز
</h2>

<p>
	تٌنشئ أداة دوكر كومبوز بيئات متعددة الحاويات للتطبيقات التي تعمل على <a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%AF%D9%88%D9%83%D8%B1-docker-r607/" rel="">دوكر</a>، كما يستخدم تعريفات الخدمة لبناء بيئات قابلة للتخصيص بالكامل مع حاويات متعددة يمكنها مشاركة الشبكات وأحجام البيانات، مما يتيح التكامل السلس بين مكونات التطبيق المختلفة.
</p>

<p>
	ننشئ ملفًا جديدًا يسمى "docker-compose.yml"، والذي يمكن أن يكون موجودًا مسبقًا ضمن مجلد الجذر Root للتطبيق، ويحدد هذا الملف بيئة الحاويات بما في ذلك الصور الأساسية لبناء التطبيق وكيفية تفاعل الخدمات ضمنه.
</p>

<p>
	نحدد ثلاث خدمات مختلفة ضمن ملف "docker-compose.yml" وهي "app" و "db" و "nginx"؛ إذ تُنشئ خدمة "app" صورةً تسمى "travellist" بناءً على ملف Dockerfile الذي أنشأناه مسبقًا، وتُشغّل الحاوية التي تحددها هذه الخدمة خادم "php-fpm" لتحليل شيفرات PHP وتُرسل النتائج مرةً أخرى إلى خدمة "nginx"، التي تعمل على حاوية منفصلة؛ بينما تحدد خدمة "mysql" حاويةً تُشغل خادم MySQL 5.7. تتصل هذه الخدمات فيما بينها باستخدام <a href="https://docs.docker.com/network/bridge/" rel="external nofollow">شبكة جسر</a> أسميناها "travellist".
</p>

<p>
	تُزامن ملفات التطبيق على كل من الخدمتين "app" و "nginx" عبر <a href="https://docs.docker.com/storage/bind-mounts/" rel="external nofollow">حوامل الربط </a>؛ إذ تُعد حوامل الربط مفيدةً في بيئات التطوير لأنها تتيح مزامنةً ثنائية الاتجاه بين الجهاز المضيف والحاويات. نُنشئ ملفًا جديدًا باسم "docker-compose.yml" في مجلد الجذر Root للتطبيق بتنفيذ الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_7693_50" style="">
<span class="pln">nano docker</span><span class="pun">-</span><span class="pln">compose</span><span class="pun">.</span><span class="pln">yml</span></pre>

<p>
	يبدأ ملف "docker-compose.yml" التقليدي برقم الإصدار يليه تعريف الخدمات "services" الخاصة بالتطبيق، كما يُضاف تعريف الشبكات التي يشاركها التطبيق في نهاية الملف، ويكون بالتالي محتوى هذا الملف على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7693_53" style="">
<span class="pln">version</span><span class="pun">:</span><span class="pln"> </span><span class="str">"3.7"</span><span class="pln">
services</span><span class="pun">:</span><span class="pln">


networks</span><span class="pun">:</span><span class="pln">
  travellist</span><span class="pun">:</span><span class="pln">
    driver</span><span class="pun">:</span><span class="pln"> bridge</span></pre>

<h3>
	خدمة التطبيق app
</h3>

<p>
	تُعِد الخدمة "app" حاويةً باسم "travellist-app" وتبني صورة دوكر جديدة بناءً على Dockerfile الموجود في نفس مسار الملف "docker-compose.yml" وتُحفظ الصورة محليًا باسم "travellist".
</p>

<p>
	نحتاج تواجد ملفات التطبيق ضمن حاوية التطبيق "app" حتى ولو كانت هذه الملفات موجودةً في جذر المستند الذي قد جرى تقديمه على أنه تطبيق موجود في حاوية "nginx"، ونحتاج ذلك من أجل تنفيذ أوامر أداة "Artisan" الخاصة بإطار عمل لارافيل على هذه الملفات. نضيف تعريف الخدمة التالي إلى الملف "docker-compose.yml":
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_7693_55" style="">
<span class="pln">  app</span><span class="pun">:</span><span class="pln">
    build</span><span class="pun">:</span><span class="pln">
      args</span><span class="pun">:</span><span class="pln">
        user</span><span class="pun">:</span><span class="pln"> sammy
        uid</span><span class="pun">:</span><span class="pln"> </span><span class="lit">1000</span><span class="pln">
      context</span><span class="pun">:</span><span class="pln"> </span><span class="pun">./</span><span class="pln">
      dockerfile</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Dockerfile</span><span class="pln">
    image</span><span class="pun">:</span><span class="pln"> travellist
    container_name</span><span class="pun">:</span><span class="pln"> travellist</span><span class="pun">-</span><span class="pln">app
    restart</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">unless</span><span class="pun">-</span><span class="pln">stopped
    working_dir</span><span class="pun">:</span><span class="pln"> </span><span class="str">/var/</span><span class="pln">www</span><span class="pun">/</span><span class="pln">
    volumes</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> </span><span class="pun">./:</span><span class="str">/var/</span><span class="pln">www
    networks</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> travellist </span></pre>

<p>
	تحقق هذه الإعدادات ما يلي:
</p>

<ul>
<li>
		<code>build</code>: تُعلِم هذه البنية الأداة دوكر كومبوز ببناء صورة محلية لخدمة التطبيق، باستخدام المسار المحدد context وملف Dockerfile للحصول على الإرشادات. تُحقن المتغيرات <code>user</code> و <code>uid</code> في Dockerfile لتخصيص أوامر إنشاء المستخدم في وقت الإنشاء.
	</li>
	<li>
		<code>image</code>: تُعد الاسم الذي يستخدم للصورة قيد الإنشاء.
	</li>
	<li>
		<code>container_name</code>: تُعِد اسم الحاوية لهذه الخدمة.
	</li>
	<li>
		<code>restart</code>: يعيد التشغيل دائمًا، ما لم يحدث إيقاف الخدمة.
	</li>
	<li>
		<code>working_dir</code>: يعيّن المجلّد الافتراضي لهذه الخدمة مثل <code>var/www/</code>.
	</li>
	<li>
		<code>volumes</code>: يُنشئ وحدة تخزين مشتركة تُزامِن المحتويات من المسار الحالي إلى <code>var/www/</code> داخل الحاوية، ويبقى موجودًا في حاوية "nginx".
	</li>
	<li>
		<code>networks</code>: تُعِد هذه الخدمة لاستخدام شبكة باسم <code>travellist</code>.
	</li>
</ul>
<h3>
	خدمة قاعدة البيانات db
</h3>

<p>
	تستخدم خدمة "db" صورة <a href="https://hub.docker.com/_/mysql" rel="external nofollow">MySQL 5.7</a> من Docker Hub. نظرًا لأن دوكر كومبوز يحمّل الملفات المتغيرة "env." الموجودة في نفس المسار الذي يحتوي الملف "docker-compose.yml"، نحصل على إعدادات قاعدة البيانات ملف "env." الذي قد ولّدناه عند إنشاء تطبيق لارافيل. نضع البيانات التالية بعد البيانات التي وضعناها للخدمة "app":
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_7693_59" style="">
<span class="pln">db</span><span class="pun">:</span><span class="pln">
    image</span><span class="pun">:</span><span class="pln"> mysql</span><span class="pun">:</span><span class="lit">5.7</span><span class="pln">
    container_name</span><span class="pun">:</span><span class="pln"> travellist</span><span class="pun">-</span><span class="pln">db
    restart</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">unless</span><span class="pun">-</span><span class="pln">stopped
    environment</span><span class="pun">:</span><span class="pln">
      MYSQL_DATABASE</span><span class="pun">:</span><span class="pln"> $</span><span class="pun">{</span><span class="pln">DB_DATABASE</span><span class="pun">}</span><span class="pln">
      MYSQL_ROOT_PASSWORD</span><span class="pun">:</span><span class="pln"> $</span><span class="pun">{</span><span class="pln">DB_PASSWORD</span><span class="pun">}</span><span class="pln">
      MYSQL_PASSWORD</span><span class="pun">:</span><span class="pln"> $</span><span class="pun">{</span><span class="pln">DB_PASSWORD</span><span class="pun">}</span><span class="pln">
      MYSQL_USER</span><span class="pun">:</span><span class="pln"> $</span><span class="pun">{</span><span class="pln">DB_USERNAME</span><span class="pun">}</span><span class="pln">
      SERVICE_TAGS</span><span class="pun">:</span><span class="pln"> dev
      SERVICE_NAME</span><span class="pun">:</span><span class="pln"> mysql
    volumes</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> </span><span class="pun">./</span><span class="pln">docker</span><span class="pun">-</span><span class="pln">compose</span><span class="pun">/</span><span class="pln">mysql</span><span class="pun">:/</span><span class="pln">docker</span><span class="pun">-</span><span class="pln">entrypoint</span><span class="pun">-</span><span class="pln">initdb</span><span class="pun">.</span><span class="pln">d
    networks</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> travellist</span></pre>

<p>
	تحقق هذه الإعدادات ما يلي:
</p>

<ul>
<li>
		<code>image</code>: تحدد صورة دوكر التي ستُستخدم في هذه الحاوية. نستخدم صورة MySQL 5.7 من Docker Hub.
	</li>
	<li>
		<code>container_name</code>: اسم الحاوية <code>travellist-db</code>.
	</li>
	<li>
		<code>restart</code>: يعيد التشغيل دائمًا ، ما لم يحدث إيقاف الخدمة.
	</li>
	<li>
		<code>environment</code>: تُحدد متغيرات البيئة في الحاوية الجديدة. نستخدم القيم التي حصلنا عليها من ملف "env." لإعداد خدمة قاعدة البيانات MySQL والتي تُنشِئ تلقائيًا قاعدة بيانات ومستخدمًا جديدًا بناءً على متغيرات البيئة المتوفرة.
	</li>
	<li>
		<code>volumes</code>: ينشئ وحدة تخزين لتهيئة قاعدة بيانات التطبيق. تستورد صورة قاعدة البيانات MySQL ملفات"sql." تلقائيًا والتي توجد في المسار <code>‎/docker-entrypoint-initdb.d</code> داخل الحاوية.
	</li>
	<li>
		<code>network</code>: تُعِد هذه الخدمة لاستخدام شبكة باسم <code>travellist</code>.
	</li>
</ul>
<h3>
	خدمة nginx
</h3>

<p>
	تستخدم خدمة "nginx" صورة <a href="https://hub.docker.com/_/nginx" rel="external nofollow">Nginx</a> مبنية مسبقًا على توزيعة لينكس اسمها <a href="https://wiki.alpinelinux.org/wiki/Main_Page" rel="external nofollow">Alpine</a>، إذ تنشئ حاويةً باسم "travellist-nginx" وتُعيد التوجيه من المنفذ "8000" الى المنفذ "80" داخل الحاوية. نضع البيانات التالية بعد بيانات خدمة "db":
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_7693_61" style="">
<span class="pln">nginx</span><span class="pun">:</span><span class="pln">
    image</span><span class="pun">:</span><span class="pln"> nginx</span><span class="pun">:</span><span class="lit">1.17</span><span class="pun">-</span><span class="pln">alpine
    container_name</span><span class="pun">:</span><span class="pln"> travellist</span><span class="pun">-</span><span class="pln">nginx
    restart</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">unless</span><span class="pun">-</span><span class="pln">stopped
    ports</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> </span><span class="lit">8000</span><span class="pun">:</span><span class="lit">80</span><span class="pln">
    volumes</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> </span><span class="pun">./:</span><span class="str">/var/</span><span class="pln">www
      </span><span class="pun">-</span><span class="pln"> </span><span class="pun">./</span><span class="pln">docker</span><span class="pun">-</span><span class="pln">compose</span><span class="pun">/</span><span class="pln">nginx</span><span class="pun">:</span><span class="str">/etc/</span><span class="pln">nginx</span><span class="pun">/</span><span class="pln">conf</span><span class="pun">.</span><span class="pln">d
    networks</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> travellist</span></pre>

<p>
	تحقق هذه الإعدادات ما يلي:
</p>

<ul>
<li>
		<code>image</code>: اسم صورة دوكر التي تستخدم في هذه الحاوية وهي في هذه الحالة الصورة Alpine Nginx 1.17 .
	</li>
	<li>
		<code>container_name</code>: اسم الحاوية <code>travellist-nginx</code>.
	</li>
	<li>
		<code>restart</code>: يعيد التشغيل دائمًا، ما لم يحدث إيقاف الخدمة.
	</li>
	<li>
		<code>ports</code>: تعيد توجيه المنفذ الذي يسمح بالوصول الخارجي عبر المنفذ "8000" إلى خادم الويب الذي يعمل على المنفذ <code>80</code> داخل الحاوية.
	</li>
	<li>
		<code>volumes</code>: تنشئ وحدتي تخزين مشتركتين، بحيث تزامن الوحدة الأولى المحتويات من نقطة العمل إلى المسار <code>var/www/</code> داخل الحاوية ويفيد ذلك بنقل التعديلات المحلية إلى التطبيق الذي يقدّمه Nginx تلقائيًا، وتنسخ الوحدة الثانية ملفات الإعداد الخاصة بخادم nginx الموجودة في الملف "docker-compose/nginx/travellist.conf" الى ملفات الضبط الخاصة بـ nginx ضمن الحاوية.
	</li>
	<li>
		<code>network</code>: تُعِد هذه الخدمة لاستخدام شبكة باسم <code>travellist</code>. وعليه يصبح ملف "docker-compose.yml" على النحو التالي:
	</li>
</ul>
<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_7693_63" style="">
<span class="pln">version</span><span class="pun">:</span><span class="pln"> </span><span class="str">"3.7"</span><span class="pln">
services</span><span class="pun">:</span><span class="pln">
  app</span><span class="pun">:</span><span class="pln">
    build</span><span class="pun">:</span><span class="pln">
      args</span><span class="pun">:</span><span class="pln">
        user</span><span class="pun">:</span><span class="pln"> sammy
        uid</span><span class="pun">:</span><span class="pln"> </span><span class="lit">1000</span><span class="pln">
      context</span><span class="pun">:</span><span class="pln"> </span><span class="pun">./</span><span class="pln">
      dockerfile</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Dockerfile</span><span class="pln">
    image</span><span class="pun">:</span><span class="pln"> travellist
    container_name</span><span class="pun">:</span><span class="pln"> travellist</span><span class="pun">-</span><span class="pln">app
    restart</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">unless</span><span class="pun">-</span><span class="pln">stopped
    working_dir</span><span class="pun">:</span><span class="pln"> </span><span class="str">/var/</span><span class="pln">www</span><span class="pun">/</span><span class="pln">
    volumes</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> </span><span class="pun">./:</span><span class="str">/var/</span><span class="pln">www
    networks</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> travellist

  db</span><span class="pun">:</span><span class="pln">
    image</span><span class="pun">:</span><span class="pln"> mysql</span><span class="pun">:</span><span class="lit">5.7</span><span class="pln">
    container_name</span><span class="pun">:</span><span class="pln"> travellist</span><span class="pun">-</span><span class="pln">db
    restart</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">unless</span><span class="pun">-</span><span class="pln">stopped
    environment</span><span class="pun">:</span><span class="pln">
      MYSQL_DATABASE</span><span class="pun">:</span><span class="pln"> $</span><span class="pun">{</span><span class="pln">DB_DATABASE</span><span class="pun">}</span><span class="pln">
      MYSQL_ROOT_PASSWORD</span><span class="pun">:</span><span class="pln"> $</span><span class="pun">{</span><span class="pln">DB_PASSWORD</span><span class="pun">}</span><span class="pln">
      MYSQL_PASSWORD</span><span class="pun">:</span><span class="pln"> $</span><span class="pun">{</span><span class="pln">DB_PASSWORD</span><span class="pun">}</span><span class="pln">
      MYSQL_USER</span><span class="pun">:</span><span class="pln"> $</span><span class="pun">{</span><span class="pln">DB_USERNAME</span><span class="pun">}</span><span class="pln">
      SERVICE_TAGS</span><span class="pun">:</span><span class="pln"> dev
      SERVICE_NAME</span><span class="pun">:</span><span class="pln"> mysql
    volumes</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> </span><span class="pun">./</span><span class="pln">docker</span><span class="pun">-</span><span class="pln">compose</span><span class="pun">/</span><span class="pln">mysql</span><span class="pun">:/</span><span class="pln">docker</span><span class="pun">-</span><span class="pln">entrypoint</span><span class="pun">-</span><span class="pln">initdb</span><span class="pun">.</span><span class="pln">d
    networks</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> travellist

  nginx</span><span class="pun">:</span><span class="pln">
    image</span><span class="pun">:</span><span class="pln"> nginx</span><span class="pun">:</span><span class="pln">alpine
    container_name</span><span class="pun">:</span><span class="pln"> travellist</span><span class="pun">-</span><span class="pln">nginx
    restart</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">unless</span><span class="pun">-</span><span class="pln">stopped
    ports</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> </span><span class="lit">8000</span><span class="pun">:</span><span class="lit">80</span><span class="pln">
    volumes</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> </span><span class="pun">./:</span><span class="str">/var/</span><span class="pln">www
      </span><span class="pun">-</span><span class="pln"> </span><span class="pun">./</span><span class="pln">docker</span><span class="pun">-</span><span class="pln">compose</span><span class="pun">/</span><span class="pln">nginx</span><span class="pun">:</span><span class="str">/etc/</span><span class="pln">nginx</span><span class="pun">/</span><span class="pln">conf</span><span class="pun">.</span><span class="pln">d</span><span class="pun">/</span><span class="pln">
    networks</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> travellist

networks</span><span class="pun">:</span><span class="pln">
  travellist</span><span class="pun">:</span><span class="pln">
    driver</span><span class="pun">:</span><span class="pln"> bridge</span></pre>

<h2>
	الخطوة 6- تشغيل التطبيق باستخدام أداة دوكر كومبوز
</h2>

<p>
	ننشئ صورة تطبيق جديد ونشغّل الخدمات التي حددناها ضمن الإعدادات باستخدام الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_7693_65" style="">
<span class="pln">docker</span><span class="pun">-</span><span class="pln">compose build app</span></pre>

<p>
	يستغرق الأمر بضعة دقائق ومن ثم يظهر الخرج التالي:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_7693_67" style="">
<span class="typ">Building</span><span class="pln"> app
</span><span class="typ">Step</span><span class="pln"> </span><span class="lit">1</span><span class="pun">/</span><span class="lit">11</span><span class="pln"> </span><span class="pun">:</span><span class="pln"> FROM php</span><span class="pun">:</span><span class="lit">7.4</span><span class="pun">-</span><span class="pln">fpm
 </span><span class="pun">---&gt;</span><span class="pln"> fa37bd6db22a
</span><span class="typ">Step</span><span class="pln"> </span><span class="lit">2</span><span class="pun">/</span><span class="lit">11</span><span class="pln"> </span><span class="pun">:</span><span class="pln"> ARG user
 </span><span class="pun">---&gt;</span><span class="pln"> </span><span class="typ">Running</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> f71eb33b7459
</span><span class="typ">Removing</span><span class="pln"> intermediate container f71eb33b7459
 </span><span class="pun">---&gt;</span><span class="pln"> </span><span class="lit">533c30216f34</span><span class="pln">
</span><span class="typ">Step</span><span class="pln"> </span><span class="lit">3</span><span class="pun">/</span><span class="lit">11</span><span class="pln"> </span><span class="pun">:</span><span class="pln"> ARG uid
 </span><span class="pun">---&gt;</span><span class="pln"> </span><span class="typ">Running</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> </span><span class="lit">60d2d2a84cda</span><span class="pln">
</span><span class="typ">Removing</span><span class="pln"> intermediate container </span><span class="lit">60d2d2a84cda</span><span class="pln">
 </span><span class="pun">---&gt;</span><span class="pln"> </span><span class="lit">497fbf904605</span><span class="pln">
</span><span class="typ">Step</span><span class="pln"> </span><span class="lit">4</span><span class="pun">/</span><span class="lit">11</span><span class="pln"> </span><span class="pun">:</span><span class="pln"> RUN apt</span><span class="pun">-</span><span class="kwd">get</span><span class="pln"> update </span><span class="pun">&amp;&amp;</span><span class="pln"> apt</span><span class="pun">-</span><span class="kwd">get</span><span class="pln"> install </span><span class="pun">-</span><span class="pln">y     git     curl     libpng</span><span class="pun">-</span><span class="pln">dev     libonig</span><span class="pun">-</span><span class="pln">dev     </span><span class="pun">...</span><span class="pln">
</span><span class="typ">Step</span><span class="pln"> </span><span class="lit">7</span><span class="pun">/</span><span class="lit">11</span><span class="pln"> </span><span class="pun">:</span><span class="pln"> COPY </span><span class="pun">--</span><span class="kwd">from</span><span class="pun">=</span><span class="pln">composer</span><span class="pun">:</span><span class="pln">latest </span><span class="pun">/</span><span class="pln">usr</span><span class="pun">/</span><span class="pln">bin</span><span class="pun">/</span><span class="pln">composer </span><span class="pun">/</span><span class="pln">usr</span><span class="pun">/</span><span class="pln">bin</span><span class="pun">/</span><span class="pln">composer
 </span><span class="pun">---&gt;</span><span class="pln"> e499f74896e3
</span><span class="typ">Step</span><span class="pln"> </span><span class="lit">8</span><span class="pun">/</span><span class="lit">11</span><span class="pln"> </span><span class="pun">:</span><span class="pln"> RUN useradd </span><span class="pun">-</span><span class="pln">G www</span><span class="pun">-</span><span class="pln">data</span><span class="pun">,</span><span class="pln">root </span><span class="pun">-</span><span class="pln">u $uid </span><span class="pun">-</span><span class="pln">d </span><span class="pun">/</span><span class="pln">home</span><span class="pun">/</span><span class="pln">$user $user
 </span><span class="pun">---&gt;</span><span class="pln"> </span><span class="typ">Running</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> </span><span class="lit">232ef9c7dbd1</span><span class="pln">
</span><span class="typ">Removing</span><span class="pln"> intermediate container </span><span class="lit">232ef9c7dbd1</span><span class="pln">
 </span><span class="pun">---&gt;</span><span class="pln"> </span><span class="lit">870fa3220ffa</span><span class="pln">
</span><span class="typ">Step</span><span class="pln"> </span><span class="lit">9</span><span class="pun">/</span><span class="lit">11</span><span class="pln"> </span><span class="pun">:</span><span class="pln"> RUN mkdir </span><span class="pun">-</span><span class="pln">p </span><span class="pun">/</span><span class="pln">home</span><span class="pun">/</span><span class="pln">$user</span><span class="pun">/.</span><span class="pln">composer </span><span class="pun">&amp;&amp;</span><span class="pln">     chown </span><span class="pun">-</span><span class="pln">R $user</span><span class="pun">:</span><span class="pln">$user </span><span class="pun">/</span><span class="pln">home</span><span class="pun">/</span><span class="pln">$user
 </span><span class="pun">---&gt;</span><span class="pln"> </span><span class="typ">Running</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> </span><span class="lit">7ca8c0cb7f09</span><span class="pln">
</span><span class="typ">Removing</span><span class="pln"> intermediate container </span><span class="lit">7ca8c0cb7f09</span><span class="pln">
 </span><span class="pun">---&gt;</span><span class="pln"> </span><span class="lit">3d2ef9519a8e</span><span class="pln">
</span><span class="typ">Step</span><span class="pln"> </span><span class="lit">10</span><span class="pun">/</span><span class="lit">11</span><span class="pln"> </span><span class="pun">:</span><span class="pln"> WORKDIR </span><span class="pun">/</span><span class="kwd">var</span><span class="pun">/</span><span class="pln">www
 </span><span class="pun">---&gt;</span><span class="pln"> </span><span class="typ">Running</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> </span><span class="lit">4a964f91edfa</span><span class="pln">
</span><span class="typ">Removing</span><span class="pln"> intermediate container </span><span class="lit">4a964f91edfa</span><span class="pln">
 </span><span class="pun">---&gt;</span><span class="pln"> </span><span class="lit">00ada639da21</span><span class="pln">
</span><span class="typ">Step</span><span class="pln"> </span><span class="lit">11</span><span class="pun">/</span><span class="lit">11</span><span class="pln"> </span><span class="pun">:</span><span class="pln"> USER $user
 </span><span class="pun">---&gt;</span><span class="pln"> </span><span class="typ">Running</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> </span><span class="lit">9f8e874fede9</span><span class="pln">
</span><span class="typ">Removing</span><span class="pln"> intermediate container </span><span class="lit">9f8e874fede9</span><span class="pln">
 </span><span class="pun">---&gt;</span><span class="pln"> fe176ff4702b

</span><span class="typ">Successfully</span><span class="pln"> built fe176ff4702b
</span><span class="typ">Successfully</span><span class="pln"> tagged travellist</span><span class="pun">:</span><span class="pln">latest</span></pre>

<p>
	نُشغّل البيئة بالخلفية بعد الانتهاء من مرحلة الإنشاء بتنفيذ الأمر:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_7693_69" style="">
<span class="pln">docker</span><span class="pun">-</span><span class="pln">compose up </span><span class="pun">-</span><span class="pln">d</span></pre>

<p>
	ويكون الخرج على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_7693_71" style="">
<span class="typ">Creating</span><span class="pln"> travellist</span><span class="pun">-</span><span class="pln">db    </span><span class="pun">...</span><span class="pln"> </span><span class="kwd">done</span><span class="pln">
</span><span class="typ">Creating</span><span class="pln"> travellist</span><span class="pun">-</span><span class="pln">app   </span><span class="pun">...</span><span class="pln"> </span><span class="kwd">done</span><span class="pln">
</span><span class="typ">Creating</span><span class="pln"> travellist</span><span class="pun">-</span><span class="pln">nginx </span><span class="pun">...</span><span class="pln"> </span><span class="kwd">done</span></pre>

<p>
	نستعرض معلومات حالة الخدمة النشطة حاليًا بتنفيذ الأمر:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_7693_73" style="">
<span class="pln">docker</span><span class="pun">-</span><span class="pln">compose ps</span></pre>

<p>
	ويكون الخرج على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_7693_75" style="">
<span class="pln">     </span><span class="typ">Name</span><span class="pln">                    </span><span class="typ">Command</span><span class="pln">              </span><span class="typ">State</span><span class="pln">          </span><span class="typ">Ports</span><span class="pln">        
</span><span class="pun">-------------------------------------------------------------------------------</span><span class="pln">
travellist</span><span class="pun">-</span><span class="pln">app     docker</span><span class="pun">-</span><span class="pln">php</span><span class="pun">-</span><span class="pln">entrypoint php</span><span class="pun">-</span><span class="pln">fpm   </span><span class="typ">Up</span><span class="pln">      </span><span class="lit">9000</span><span class="pun">/</span><span class="pln">tcp            
travellist</span><span class="pun">-</span><span class="pln">db      docker</span><span class="pun">-</span><span class="pln">entrypoint</span><span class="pun">.</span><span class="pln">sh mysqld     </span><span class="typ">Up</span><span class="pln">      </span><span class="lit">3306</span><span class="pun">/</span><span class="pln">tcp</span><span class="pun">,</span><span class="pln"> </span><span class="lit">33060</span><span class="pun">/</span><span class="pln">tcp 
travellist</span><span class="pun">-</span><span class="pln">nginx   nginx </span><span class="pun">-</span><span class="pln">g daemon off</span><span class="pun">;</span><span class="pln">            </span><span class="typ">Up</span><span class="pln">      </span><span class="lit">0.0</span><span class="pun">.</span><span class="lit">0.0</span><span class="pun">:</span><span class="lit">8000</span><span class="pun">-&gt;</span><span class="lit">80</span><span class="pun">/</span><span class="pln">tcp</span></pre>

<p>
	بيئتنا الآن قيد التشغيل، لكننا ما زلنا بحاجة إلى تنفيذ أمرين لإنهاء إعداد التطبيق، إذ نستخدم الأمر <code>docker-compose exec</code> لتنفيذ جميع الأوامر ضمن الحاويات، فلو رغبنا بتنفيذ الأمر <code>ls –l</code> الذي يظهر معلومات تفصيلية حول الملفات في مسار التطبيق، ننفذ الأمر التالي وتظهر النتائج كما يلي:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_7693_78" style="">
<span class="pln">docker</span><span class="pun">-</span><span class="pln">compose </span><span class="kwd">exec</span><span class="pln"> app ls </span><span class="pun">-</span><span class="pln">l</span></pre>

<p>
	ويكون الخرج على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_7693_80" style="">
<span class="pln">total </span><span class="lit">256</span><span class="pln">
</span><span class="pun">-</span><span class="pln">rw</span><span class="pun">-</span><span class="pln">rw</span><span class="pun">-</span><span class="pln">r</span><span class="pun">--</span><span class="pln"> </span><span class="lit">1</span><span class="pln"> sammy </span><span class="lit">1001</span><span class="pln">    </span><span class="lit">738</span><span class="pln"> </span><span class="typ">Jan</span><span class="pln"> </span><span class="lit">15</span><span class="pln"> </span><span class="lit">16</span><span class="pun">:</span><span class="lit">46</span><span class="pln"> </span><span class="typ">Dockerfile</span><span class="pln">
</span><span class="pun">-</span><span class="pln">rw</span><span class="pun">-</span><span class="pln">rw</span><span class="pun">-</span><span class="pln">r</span><span class="pun">--</span><span class="pln"> </span><span class="lit">1</span><span class="pln"> sammy </span><span class="lit">1001</span><span class="pln">    </span><span class="lit">101</span><span class="pln"> </span><span class="typ">Jan</span><span class="pln">  </span><span class="lit">7</span><span class="pln"> </span><span class="lit">08</span><span class="pun">:</span><span class="lit">05</span><span class="pln"> README</span><span class="pun">.</span><span class="pln">md
drwxrwxr</span><span class="pun">-</span><span class="pln">x </span><span class="lit">6</span><span class="pln"> sammy </span><span class="lit">1001</span><span class="pln">   </span><span class="lit">4096</span><span class="pln"> </span><span class="typ">Jan</span><span class="pln">  </span><span class="lit">7</span><span class="pln"> </span><span class="lit">08</span><span class="pun">:</span><span class="lit">05</span><span class="pln"> app
</span><span class="pun">-</span><span class="pln">rwxr</span><span class="pun">-</span><span class="pln">xr</span><span class="pun">-</span><span class="pln">x </span><span class="lit">1</span><span class="pln"> sammy </span><span class="lit">1001</span><span class="pln">   </span><span class="lit">1686</span><span class="pln"> </span><span class="typ">Jan</span><span class="pln">  </span><span class="lit">7</span><span class="pln"> </span><span class="lit">08</span><span class="pun">:</span><span class="lit">05</span><span class="pln"> artisan
drwxrwxr</span><span class="pun">-</span><span class="pln">x </span><span class="lit">3</span><span class="pln"> sammy </span><span class="lit">1001</span><span class="pln">   </span><span class="lit">4096</span><span class="pln"> </span><span class="typ">Jan</span><span class="pln">  </span><span class="lit">7</span><span class="pln"> </span><span class="lit">08</span><span class="pun">:</span><span class="lit">05</span><span class="pln"> bootstrap
</span><span class="pun">-</span><span class="pln">rw</span><span class="pun">-</span><span class="pln">rw</span><span class="pun">-</span><span class="pln">r</span><span class="pun">--</span><span class="pln"> </span><span class="lit">1</span><span class="pln"> sammy </span><span class="lit">1001</span><span class="pln">   </span><span class="lit">1501</span><span class="pln"> </span><span class="typ">Jan</span><span class="pln">  </span><span class="lit">7</span><span class="pln"> </span><span class="lit">08</span><span class="pun">:</span><span class="lit">05</span><span class="pln"> composer</span><span class="pun">.</span><span class="pln">json
</span><span class="pun">-</span><span class="pln">rw</span><span class="pun">-</span><span class="pln">rw</span><span class="pun">-</span><span class="pln">r</span><span class="pun">--</span><span class="pln"> </span><span class="lit">1</span><span class="pln"> sammy </span><span class="lit">1001</span><span class="pln"> </span><span class="lit">179071</span><span class="pln"> </span><span class="typ">Jan</span><span class="pln">  </span><span class="lit">7</span><span class="pln"> </span><span class="lit">08</span><span class="pun">:</span><span class="lit">05</span><span class="pln"> composer</span><span class="pun">.</span><span class="kwd">lock</span><span class="pln">
drwxrwxr</span><span class="pun">-</span><span class="pln">x </span><span class="lit">2</span><span class="pln"> sammy </span><span class="lit">1001</span><span class="pln">   </span><span class="lit">4096</span><span class="pln"> </span><span class="typ">Jan</span><span class="pln">  </span><span class="lit">7</span><span class="pln"> </span><span class="lit">08</span><span class="pun">:</span><span class="lit">05</span><span class="pln"> config
drwxrwxr</span><span class="pun">-</span><span class="pln">x </span><span class="lit">5</span><span class="pln"> sammy </span><span class="lit">1001</span><span class="pln">   </span><span class="lit">4096</span><span class="pln"> </span><span class="typ">Jan</span><span class="pln">  </span><span class="lit">7</span><span class="pln"> </span><span class="lit">08</span><span class="pun">:</span><span class="lit">05</span><span class="pln"> database
drwxrwxr</span><span class="pun">-</span><span class="pln">x </span><span class="lit">4</span><span class="pln"> sammy </span><span class="lit">1001</span><span class="pln">   </span><span class="lit">4096</span><span class="pln"> </span><span class="typ">Jan</span><span class="pln"> </span><span class="lit">15</span><span class="pln"> </span><span class="lit">16</span><span class="pun">:</span><span class="lit">46</span><span class="pln"> docker</span><span class="pun">-</span><span class="pln">compose
</span><span class="pun">-</span><span class="pln">rw</span><span class="pun">-</span><span class="pln">rw</span><span class="pun">-</span><span class="pln">r</span><span class="pun">--</span><span class="pln"> </span><span class="lit">1</span><span class="pln"> sammy </span><span class="lit">1001</span><span class="pln">   </span><span class="lit">1015</span><span class="pln"> </span><span class="typ">Jan</span><span class="pln"> </span><span class="lit">15</span><span class="pln"> </span><span class="lit">16</span><span class="pun">:</span><span class="lit">45</span><span class="pln"> docker</span><span class="pun">-</span><span class="pln">compose</span><span class="pun">.</span><span class="pln">yml
</span><span class="pun">-</span><span class="pln">rw</span><span class="pun">-</span><span class="pln">rw</span><span class="pun">-</span><span class="pln">r</span><span class="pun">--</span><span class="pln"> </span><span class="lit">1</span><span class="pln"> sammy </span><span class="lit">1001</span><span class="pln">   </span><span class="lit">1013</span><span class="pln"> </span><span class="typ">Jan</span><span class="pln">  </span><span class="lit">7</span><span class="pln"> </span><span class="lit">08</span><span class="pun">:</span><span class="lit">05</span><span class="pln"> </span><span class="kwd">package</span><span class="pun">.</span><span class="pln">json
</span><span class="pun">-</span><span class="pln">rw</span><span class="pun">-</span><span class="pln">rw</span><span class="pun">-</span><span class="pln">r</span><span class="pun">--</span><span class="pln"> </span><span class="lit">1</span><span class="pln"> sammy </span><span class="lit">1001</span><span class="pln">   </span><span class="lit">1405</span><span class="pln"> </span><span class="typ">Jan</span><span class="pln">  </span><span class="lit">7</span><span class="pln"> </span><span class="lit">08</span><span class="pun">:</span><span class="lit">05</span><span class="pln"> phpunit</span><span class="pun">.</span><span class="pln">xml
drwxrwxr</span><span class="pun">-</span><span class="pln">x </span><span class="lit">2</span><span class="pln"> sammy </span><span class="lit">1001</span><span class="pln">   </span><span class="lit">4096</span><span class="pln"> </span><span class="typ">Jan</span><span class="pln">  </span><span class="lit">7</span><span class="pln"> </span><span class="lit">08</span><span class="pun">:</span><span class="lit">05</span><span class="pln"> </span><span class="kwd">public</span><span class="pln">
</span><span class="pun">-</span><span class="pln">rw</span><span class="pun">-</span><span class="pln">rw</span><span class="pun">-</span><span class="pln">r</span><span class="pun">--</span><span class="pln"> </span><span class="lit">1</span><span class="pln"> sammy </span><span class="lit">1001</span><span class="pln">    </span><span class="lit">273</span><span class="pln"> </span><span class="typ">Jan</span><span class="pln">  </span><span class="lit">7</span><span class="pln"> </span><span class="lit">08</span><span class="pun">:</span><span class="lit">05</span><span class="pln"> readme</span><span class="pun">.</span><span class="pln">md
drwxrwxr</span><span class="pun">-</span><span class="pln">x </span><span class="lit">6</span><span class="pln"> sammy </span><span class="lit">1001</span><span class="pln">   </span><span class="lit">4096</span><span class="pln"> </span><span class="typ">Jan</span><span class="pln">  </span><span class="lit">7</span><span class="pln"> </span><span class="lit">08</span><span class="pun">:</span><span class="lit">05</span><span class="pln"> resources
drwxrwxr</span><span class="pun">-</span><span class="pln">x </span><span class="lit">2</span><span class="pln"> sammy </span><span class="lit">1001</span><span class="pln">   </span><span class="lit">4096</span><span class="pln"> </span><span class="typ">Jan</span><span class="pln">  </span><span class="lit">7</span><span class="pln"> </span><span class="lit">08</span><span class="pun">:</span><span class="lit">05</span><span class="pln"> routes
</span><span class="pun">-</span><span class="pln">rw</span><span class="pun">-</span><span class="pln">rw</span><span class="pun">-</span><span class="pln">r</span><span class="pun">--</span><span class="pln"> </span><span class="lit">1</span><span class="pln"> sammy </span><span class="lit">1001</span><span class="pln">    </span><span class="lit">563</span><span class="pln"> </span><span class="typ">Jan</span><span class="pln">  </span><span class="lit">7</span><span class="pln"> </span><span class="lit">08</span><span class="pun">:</span><span class="lit">05</span><span class="pln"> server</span><span class="pun">.</span><span class="pln">php
drwxrwxr</span><span class="pun">-</span><span class="pln">x </span><span class="lit">5</span><span class="pln"> sammy </span><span class="lit">1001</span><span class="pln">   </span><span class="lit">4096</span><span class="pln"> </span><span class="typ">Jan</span><span class="pln">  </span><span class="lit">7</span><span class="pln"> </span><span class="lit">08</span><span class="pun">:</span><span class="lit">05</span><span class="pln"> storage
drwxrwxr</span><span class="pun">-</span><span class="pln">x </span><span class="lit">4</span><span class="pln"> sammy </span><span class="lit">1001</span><span class="pln">   </span><span class="lit">4096</span><span class="pln"> </span><span class="typ">Jan</span><span class="pln">  </span><span class="lit">7</span><span class="pln"> </span><span class="lit">08</span><span class="pun">:</span><span class="lit">05</span><span class="pln"> tests
</span><span class="pun">-</span><span class="pln">rw</span><span class="pun">-</span><span class="pln">rw</span><span class="pun">-</span><span class="pln">r</span><span class="pun">--</span><span class="pln"> </span><span class="lit">1</span><span class="pln"> sammy </span><span class="lit">1001</span><span class="pln">    </span><span class="lit">538</span><span class="pln"> </span><span class="typ">Jan</span><span class="pln">  </span><span class="lit">7</span><span class="pln"> </span><span class="lit">08</span><span class="pun">:</span><span class="lit">05</span><span class="pln"> webpack</span><span class="pun">.</span><span class="pln">mix</span><span class="pun">.</span><span class="pln">js</span></pre>

<p>
	تُثبّت اعتماديات التطبيق باستخدام الأداة <code>composer install</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_7693_82" style="">
<span class="pln">docker</span><span class="pun">-</span><span class="pln">compose </span><span class="kwd">exec</span><span class="pln"> app composer install</span></pre>

<p>
	يظهر الخرج التالي بعد إتمام العملية:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_7693_84" style="">
<span class="typ">Loading</span><span class="pln"> composer repositories </span><span class="kwd">with</span><span class="pln"> </span><span class="kwd">package</span><span class="pln"> information
</span><span class="typ">Installing</span><span class="pln"> dependencies </span><span class="pun">(</span><span class="pln">including </span><span class="kwd">require</span><span class="pun">-</span><span class="pln">dev</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">from</span><span class="pln"> </span><span class="kwd">lock</span><span class="pln"> file
</span><span class="typ">Package</span><span class="pln"> operations</span><span class="pun">:</span><span class="pln"> </span><span class="lit">85</span><span class="pln"> installs</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pln"> updates</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pln"> removals
  </span><span class="pun">-</span><span class="pln"> </span><span class="typ">Installing</span><span class="pln"> doctrine</span><span class="pun">/</span><span class="pln">inflector </span><span class="pun">(</span><span class="lit">1.3</span><span class="pun">.</span><span class="lit">1</span><span class="pun">):</span><span class="pln"> </span><span class="typ">Downloading</span><span class="pln"> </span><span class="pun">(</span><span class="lit">100</span><span class="pun">%)</span><span class="pln">         
  </span><span class="pun">-</span><span class="pln"> </span><span class="typ">Installing</span><span class="pln"> doctrine</span><span class="pun">/</span><span class="pln">lexer </span><span class="pun">(</span><span class="lit">1.2</span><span class="pun">.</span><span class="lit">0</span><span class="pun">):</span><span class="pln"> </span><span class="typ">Downloading</span><span class="pln"> </span><span class="pun">(</span><span class="lit">100</span><span class="pun">%)</span><span class="pln">         
  </span><span class="pun">-</span><span class="pln"> </span><span class="typ">Installing</span><span class="pln"> dragonmantank</span><span class="pun">/</span><span class="pln">cron</span><span class="pun">-</span><span class="pln">expression </span><span class="pun">(</span><span class="pln">v2</span><span class="pun">.</span><span class="lit">3.0</span><span class="pun">):</span><span class="pln"> </span><span class="typ">Downloading</span><span class="pln"> </span><span class="pun">(</span><span class="lit">100</span><span class="pun">%)</span><span class="pln">         
  </span><span class="pun">-</span><span class="pln"> </span><span class="typ">Installing</span><span class="pln"> erusev</span><span class="pun">/</span><span class="pln">parsedown </span><span class="pun">(</span><span class="lit">1.7</span><span class="pun">.</span><span class="lit">4</span><span class="pun">):</span><span class="pln"> </span><span class="typ">Downloading</span><span class="pln"> </span><span class="pun">(</span><span class="lit">100</span><span class="pun">%)</span><span class="pln">         
  </span><span class="pun">-</span><span class="pln"> </span><span class="typ">Installing</span><span class="pln"> symfony</span><span class="pun">/</span><span class="pln">polyfill</span><span class="pun">-</span><span class="pln">ctype </span><span class="pun">(</span><span class="pln">v1</span><span class="pun">.</span><span class="lit">13.1</span><span class="pun">):</span><span class="pln"> </span><span class="typ">Downloading</span><span class="pln"> </span><span class="pun">(</span><span class="lit">100</span><span class="pun">%)</span><span class="pln">         
  </span><span class="pun">-</span><span class="pln"> </span><span class="typ">Installing</span><span class="pln"> phpoption</span><span class="pun">/</span><span class="pln">phpoption </span><span class="pun">(</span><span class="lit">1.7</span><span class="pun">.</span><span class="lit">2</span><span class="pun">):</span><span class="pln"> </span><span class="typ">Downloading</span><span class="pln"> </span><span class="pun">(</span><span class="lit">100</span><span class="pun">%)</span><span class="pln">         
  </span><span class="pun">-</span><span class="pln"> </span><span class="typ">Installing</span><span class="pln"> vlucas</span><span class="pun">/</span><span class="pln">phpdotenv </span><span class="pun">(</span><span class="pln">v3</span><span class="pun">.</span><span class="lit">6.0</span><span class="pun">):</span><span class="pln"> </span><span class="typ">Downloading</span><span class="pln"> </span><span class="pun">(</span><span class="lit">100</span><span class="pun">%)</span><span class="pln">         
  </span><span class="pun">-</span><span class="pln"> </span><span class="typ">Installing</span><span class="pln"> symfony</span><span class="pun">/</span><span class="pln">css</span><span class="pun">-</span><span class="pln">selector </span><span class="pun">(</span><span class="pln">v5</span><span class="pun">.</span><span class="lit">0.2</span><span class="pun">):</span><span class="pln"> </span><span class="typ">Downloading</span><span class="pln"> </span><span class="pun">(</span><span class="lit">100</span><span class="pun">%)</span><span class="pln">        
</span><span class="pun">…</span><span class="pln">
</span><span class="typ">Generating</span><span class="pln"> optimized autoload files
</span><span class="pun">&gt;</span><span class="pln"> </span><span class="typ">Illuminate</span><span class="pln">\Foundation\ComposerScripts</span><span class="pun">::</span><span class="pln">postAutoloadDump
</span><span class="pun">&gt;</span><span class="pln"> </span><span class="lit">@php</span><span class="pln"> artisan </span><span class="kwd">package</span><span class="pun">:</span><span class="pln">discover </span><span class="pun">--</span><span class="pln">ansi
</span><span class="typ">Discovered</span><span class="pln"> </span><span class="typ">Package</span><span class="pun">:</span><span class="pln"> facade</span><span class="pun">/</span><span class="pln">ignition
</span><span class="typ">Discovered</span><span class="pln"> </span><span class="typ">Package</span><span class="pun">:</span><span class="pln"> fideloper</span><span class="pun">/</span><span class="pln">proxy
</span><span class="typ">Discovered</span><span class="pln"> </span><span class="typ">Package</span><span class="pun">:</span><span class="pln"> laravel</span><span class="pun">/</span><span class="pln">tinker
</span><span class="typ">Discovered</span><span class="pln"> </span><span class="typ">Package</span><span class="pun">:</span><span class="pln"> nesbot</span><span class="pun">/</span><span class="pln">carbon
</span><span class="typ">Discovered</span><span class="pln"> </span><span class="typ">Package</span><span class="pun">:</span><span class="pln"> nunomaduro</span><span class="pun">/</span><span class="pln">collision
</span><span class="typ">Package</span><span class="pln"> manifest generated successfully</span><span class="pun">.</span></pre>

<p>
	نولّد مفتاح التطبيق الخاص باستخدام أداة <code>artisan</code> الخاصة بلارافيل، إذ يشفّر هذا المفتاح جلسات المستخدم والبيانات الحساسة:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_7693_90" style="">
<span class="pln">docker</span><span class="pun">-</span><span class="pln">compose </span><span class="kwd">exec</span><span class="pln"> app php artisan key</span><span class="pun">:</span><span class="pln">generate</span></pre>

<p>
	ويكون الخرج على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_7693_88" style="">
<span class="typ">Application</span><span class="pln"> key </span><span class="kwd">set</span><span class="pln"> successfully</span><span class="pun">.</span></pre>

<p>
	نطلب الرابط التالي من المتصفح:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_7693_92" style="">
<span class="pln">http</span><span class="pun">:</span><span class="com">//server_domain_or_IP:8000</span></pre>

<p>
	يظهر عندها الخرج التالي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="109249" href="https://academy.hsoub.com/uploads/monthly_2022_10/browser_responce.jpg.08a7de04a4aae0c568d5a0fa99a32d35.jpg" rel=""><img alt="browser_responce.jpg" class="ipsImage ipsImage_thumbnailed" data-fileid="109249" data-unique="dtb0h0bk0" src="https://academy.hsoub.com/uploads/monthly_2022_10/browser_responce.jpg.08a7de04a4aae0c568d5a0fa99a32d35.jpg"></a>
</p>

<p>
	نستطيع استخدام الأمر<code>logs</code> لفحص السجلات التي تولّدها الخدمة بتنفيذ الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_7693_94" style="">
<span class="pln">docker</span><span class="pun">-</span><span class="pln">compose logs nginx</span></pre>

<p>
	ويكون الخرج على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_7693_96" style="">
<span class="typ">Attaching</span><span class="pln"> to travellist</span><span class="pun">-</span><span class="pln">nginx
travellist</span><span class="pun">-</span><span class="pln">nginx </span><span class="pun">|</span><span class="pln"> </span><span class="lit">192.168</span><span class="pun">.</span><span class="lit">160.1</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">[</span><span class="lit">23</span><span class="pun">/</span><span class="typ">Jan</span><span class="pun">/</span><span class="lit">2020</span><span class="pun">:</span><span class="lit">13</span><span class="pun">:</span><span class="lit">57</span><span class="pun">:</span><span class="lit">25</span><span class="pln"> </span><span class="pun">+</span><span class="lit">0000</span><span class="pun">]</span><span class="pln"> </span><span class="str">"GET / HTTP/1.1"</span><span class="pln"> </span><span class="lit">200</span><span class="pln"> </span><span class="lit">626</span><span class="pln"> </span><span class="str">"-"</span><span class="pln"> </span><span class="str">"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36"</span><span class="pln">
travellist</span><span class="pun">-</span><span class="pln">nginx </span><span class="pun">|</span><span class="pln"> </span><span class="lit">192.168</span><span class="pun">.</span><span class="lit">160.1</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">[</span><span class="lit">23</span><span class="pun">/</span><span class="typ">Jan</span><span class="pun">/</span><span class="lit">2020</span><span class="pun">:</span><span class="lit">13</span><span class="pun">:</span><span class="lit">57</span><span class="pun">:</span><span class="lit">26</span><span class="pln"> </span><span class="pun">+</span><span class="lit">0000</span><span class="pun">]</span><span class="pln"> </span><span class="str">"GET /favicon.ico HTTP/1.1"</span><span class="pln"> </span><span class="lit">200</span><span class="pln"> </span><span class="lit">0</span><span class="pln"> </span><span class="str">"http://localhost:8000/"</span><span class="pln"> </span><span class="str">"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36"</span><span class="pln">
travellist</span><span class="pun">-</span><span class="pln">nginx </span><span class="pun">|</span><span class="pln"> </span><span class="lit">192.168</span><span class="pun">.</span><span class="lit">160.1</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">[</span><span class="lit">23</span><span class="pun">/</span><span class="typ">Jan</span><span class="pun">/</span><span class="lit">2020</span><span class="pun">:</span><span class="lit">13</span><span class="pun">:</span><span class="lit">57</span><span class="pun">:</span><span class="lit">42</span><span class="pln"> </span><span class="pun">+</span><span class="lit">0000</span><span class="pun">]</span><span class="pln"> </span><span class="str">"GET / HTTP/1.1"</span><span class="pln"> </span><span class="lit">200</span><span class="pln"> </span><span class="lit">626</span><span class="pln"> </span><span class="str">"-"</span><span class="pln"> </span><span class="str">"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36"</span><span class="pln">
</span><span class="pun">…</span></pre>

<p>
	نستطيع إيقاف العمل مؤقتًا لبيئة دوكر كومبوز مع الحفاظ على حالة الخدمات بتشغيل الأمر:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_7693_98" style="">
<span class="typ">Docker</span><span class="pun">-</span><span class="pln">compose puse</span></pre>

<p>
	ويكون الخرج على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_7693_100" style="">
<span class="typ">Pausing</span><span class="pln"> travellist</span><span class="pun">-</span><span class="pln">db    </span><span class="pun">...</span><span class="pln"> </span><span class="kwd">done</span><span class="pln">
</span><span class="typ">Pausing</span><span class="pln"> travellist</span><span class="pun">-</span><span class="pln">nginx </span><span class="pun">...</span><span class="pln"> </span><span class="kwd">done</span><span class="pln">
</span><span class="typ">Pausing</span><span class="pln"> travellist</span><span class="pun">-</span><span class="pln">app   </span><span class="pun">...</span><span class="pln"> </span><span class="kwd">done</span></pre>

<p>
	نستطيع استئناف العمل بتنفيذ الأمر:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_7693_102" style="">
<span class="pln">docker</span><span class="pun">-</span><span class="pln">compose unpause</span></pre>

<p>
	ويكون الخرج على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_7693_104" style="">
<span class="typ">Unpausing</span><span class="pln"> travellist</span><span class="pun">-</span><span class="pln">app   </span><span class="pun">...</span><span class="pln"> </span><span class="kwd">done</span><span class="pln">
</span><span class="typ">Unpausing</span><span class="pln"> travellist</span><span class="pun">-</span><span class="pln">nginx </span><span class="pun">...</span><span class="pln"> </span><span class="kwd">done</span><span class="pln">
</span><span class="typ">Unpausing</span><span class="pln"> travellist</span><span class="pun">-</span><span class="pln">db    </span><span class="pun">...</span><span class="pln"> </span><span class="kwd">done</span></pre>

<p>
	نستطيع إيقاف عمل بيئة كومبوز ونزيل جميع حاوياتها وشبكاتها ووحدات تخزينها بتنفيذ الأمر:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_7693_106" style="">
<span class="pln">docker</span><span class="pun">-</span><span class="pln">compose down</span></pre>

<p>
	ويكون الخرج على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_7693_108" style="">
<span class="typ">Stopping</span><span class="pln"> travellist</span><span class="pun">-</span><span class="pln">nginx </span><span class="pun">...</span><span class="pln"> </span><span class="kwd">done</span><span class="pln">
</span><span class="typ">Stopping</span><span class="pln"> travellist</span><span class="pun">-</span><span class="pln">db    </span><span class="pun">...</span><span class="pln"> </span><span class="kwd">done</span><span class="pln">
</span><span class="typ">Stopping</span><span class="pln"> travellist</span><span class="pun">-</span><span class="pln">app   </span><span class="pun">...</span><span class="pln"> </span><span class="kwd">done</span><span class="pln">
</span><span class="typ">Removing</span><span class="pln"> travellist</span><span class="pun">-</span><span class="pln">nginx </span><span class="pun">...</span><span class="pln"> </span><span class="kwd">done</span><span class="pln">
</span><span class="typ">Removing</span><span class="pln"> travellist</span><span class="pun">-</span><span class="pln">db    </span><span class="pun">...</span><span class="pln"> </span><span class="kwd">done</span><span class="pln">
</span><span class="typ">Removing</span><span class="pln"> travellist</span><span class="pun">-</span><span class="pln">app   </span><span class="pun">...</span><span class="pln"> </span><span class="kwd">done</span><span class="pln">
</span><span class="typ">Removing</span><span class="pln"> network travellist</span><span class="pun">-</span><span class="pln">laravel</span><span class="pun">-</span><span class="pln">demo_travellist</span></pre>

<h2>
	الخاتمة
</h2>

<p>
	أعددنا بيئة دوكر مكونة من ثلاث حاويات باستخدام الأداة دوكر كومبوز وحددنا بنيته الأساسية في ملف YAML. يمكن العمل على تطبيق لارافيل دون الحاجة إلى تثبيت <a href="https://academy.hsoub.com/devops/servers/%D8%AF%D9%84%D9%8A%D9%84-%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%AE%D8%A7%D8%AF%D9%85-%D9%88%D9%8A%D8%A8-%D9%85%D8%AD%D9%84%D9%8A-%D8%AE%D8%B7%D9%88%D8%A9-%D8%A8%D8%AE%D8%B7%D9%88%D8%A9-r422/" rel="">خادم ويب محلي</a> وإعداده للتطوير والاختبار، ويتحقق كل ذلك باستخدام بيئة معزولة يمكن تكرارها وإعادة إنشائها بسهولة حتى الوصول إلى التطبيق المطلوب تثبيته ضمن بيئات الإنتاج.
</p>

<p>
	ترجمة -وبتصرّف- للمقال <a href="https://www.digitalocean.com/community/tutorials/how-to-containerize-a-laravel-application-for-development-with-docker-compose-on-ubuntu-18-04" rel="external nofollow">How To Containerize a Laravel Application for Development with Docker Compose on Ubuntu 18.04</a> لصاحبه Erika Heidi.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D9%84%D8%A7%D8%B1%D8%A7%D9%81%D9%8A%D9%84-laravel-%D8%B9%D9%84%D9%89-%D8%AF%D9%88%D9%83%D8%B1-%D9%83%D9%88%D9%85%D8%A8%D9%88%D8%B2-docker-compose-r650/" rel="">تثبيت وإعداد لارافيل Laravel على دوكر كومبوز Docker Compose</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-docker-compose-%D8%B9%D9%84%D9%89-%D8%AF%D8%A8%D9%8A%D8%A7%D9%86-r464/" rel="">كيفية تثبيت Docker Compose على دبيان</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/nodejs/%D9%86%D9%82%D9%84-%D8%B3%D9%8A%D8%B1-%D8%B9%D9%85%D9%84-docker-compose-%D8%A5%D9%84%D9%89-kubernetes-r812/" rel="">نقل سير عمل Docker Compose إلى Kubernetes</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D9%84%D8%A7%D8%B1%D8%A7%D9%81%D9%8A%D9%84-%D9%85%D8%B9-%D8%AE%D8%A7%D8%AF%D9%85-nginx-%D9%88%D9%82%D8%A7%D8%B9%D8%AF%D8%A9-%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-mysql-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%AF%D9%88%D9%83%D8%B1-%D9%83%D9%88%D9%85%D8%A8%D9%88%D8%B2-docker-compose-r651/" rel="">إعداد لارافيل مع خادم Nginx وقاعدة بيانات MySQL باستخدام دوكر كومبوز Docker Compose</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">652</guid><pubDate>Fri, 07 Oct 2022 10:31:43 +0000</pubDate></item><item><title>&#x625;&#x639;&#x62F;&#x627;&#x62F; &#x644;&#x627;&#x631;&#x627;&#x641;&#x64A;&#x644; &#x645;&#x639; &#x62E;&#x627;&#x62F;&#x645; Nginx &#x648;&#x642;&#x627;&#x639;&#x62F;&#x629; &#x628;&#x64A;&#x627;&#x646;&#x627;&#x62A; MySQL &#x628;&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x62F;&#x648;&#x643;&#x631; &#x643;&#x648;&#x645;&#x628;&#x648;&#x632; Docker Compose</title><link>https://academy.hsoub.com/devops/cloud-computing/docker/%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D9%84%D8%A7%D8%B1%D8%A7%D9%81%D9%8A%D9%84-%D9%85%D8%B9-%D8%AE%D8%A7%D8%AF%D9%85-nginx-%D9%88%D9%82%D8%A7%D8%B9%D8%AF%D8%A9-%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-mysql-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%AF%D9%88%D9%83%D8%B1-%D9%83%D9%88%D9%85%D8%A8%D9%88%D8%B2-docker-compose-r651/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_10/633fdbedd0d3b_----Nginx---MySQL--Docker-Compose.jpg.1840918393beb2375c0968ecbee5ced5.jpg" /></p>

<p>
	يُعد دوكر الحل الأكثر شيوعًا لنشر التطبيقات بسبب ما يقدمه من تبسيط لعملية تشغيل التطبيقات ونشرها في <a href="https://www.docker.com/resources/what-container/" rel="external nofollow">حاويات</a> مؤقتة. يمكن استخدام دوكر لإنشاء حاويات تشكل <a href="https://academy.hsoub.com/devops/linux/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D8%AD%D8%B2%D9%85%D8%A9-lemp-%D8%B9%D9%84%D9%89-%D8%A3%D9%88%D8%A8%D9%88%D9%86%D8%AA%D9%88-1804-r436/" rel="">حزمة تطبيقات LEMP</a> مع خوادم PHP و <a href="https://academy.hsoub.com/devops/servers/web/nginx/%D8%AA%D9%86%D8%B5%D9%8A%D8%A8%D8%8C-%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D9%88%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-nginx-%D9%83%D8%AE%D8%A7%D8%AF%D9%88%D9%85-%D9%88%D9%8A%D8%A8-r1/" rel="">Nginx</a> و MySQL وإطار عمل لارافيل من أجل تسهيل عملية إنشاء التطبيقات. يسمح <a href="https://docs.docker.com/compose/" rel="external nofollow">Docker Compose</a> للمطورين بتعريف البنية التحتية بما فيها من خدمات وشبكات ووحدات تخزين في ملف واحد ويوفر بديلًا فعالًا لتشغيل الأمرين التاليين:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_3536_56" style="">
<span class="pln">docker container create
docker container run</span></pre>

<p>
	سننشئ تطبيقًا باستخدام إطار عمل لارافيل، مع <a href="https://academy.hsoub.com/devops/servers/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%AE%D8%A7%D8%AF%D9%85-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-r574/" rel="">خادم ويب</a> Nginx و<a href="https://academy.hsoub.com/devops/servers/databases/mysql/%D8%AA%D8%B9%D9%84%D9%85-%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-mysql-r297/" rel="">قاعدة بيانات MySQL</a>، إذ تتواجد هذه المكونات ضمن حاويات دوكر، وسنحدد تكوين المكدس كاملًا ضمن ملف "docker-compose"، جنبًا إلى جنب مع ملفات التهيئة <a href="https://academy.hsoub.com/programming/php/%D8%AA%D9%85%D9%87%D9%8A%D8%AF-%D8%A5%D9%84%D9%89-%D9%84%D8%BA%D8%A9-php-r235/" rel="">للغة PHP</a> وقاعدة بيانات MySQL وخادم Nginx.
</p>

<h2>
	المتطلبات الأساسية
</h2>

<ul>
<li>
		الوصول إلى خادم يعمل <a href="https://academy.hsoub.com/devops/linux/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D8%AA%D9%88%D8%B2%D9%8A%D8%B9%D8%A9-%D8%A3%D9%88%D8%A8%D9%86%D8%AA%D9%88-%D9%85%D9%86-%D9%84%D9%8A%D9%86%D9%83%D8%B3-%D8%A8%D8%A3%D8%A8%D8%B3%D8%B7-%D8%B7%D8%B1%D9%8A%D9%82%D8%A9-r575/" rel="">بنظام التشغيل أوبنتو Ubuntu</a> ذي رقم إصدار 18.04 أو إلى خادم تطوير يعمل بنفس نظام التشغيل المذكور بواسطة مستخدم ذي صلاحيات إدارة sudo ولا يفضل استخدام حساب الجذر root لأسباب تتعلق بأمان الخادم، كما يُفضّل وجود جدار حماية نشط في حال العمل على خادم بعيد.
	</li>
	<li>
		تثبيت الأداة دوكر على الخادم.
	</li>
	<li>
		تثبيت الأداة دوكر كومبوز Docker Compose على الخادم.
	</li>
</ul>
<h2>
	الخطوة 1- تنزيل لارافيل وتثبيت اعتمادياتها
</h2>

<p>
	لا بُد من تنزيل أحدث إصدار متاح من لارافيل وتثبيت الاعتماديات اللازمة للمشروع البرمجي بما فيها <a href="https://github.com/composer/docker" rel="external nofollow">Composer</a>، مدير الحزم الخاص بلغة PHP في دوكر. نثبّت أحدث إصدار من لارافيل داخل مجلد <code>laravel-app</code> ضمن المسار الرئيسي للنظام كما يلي:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_3536_58" style="">
<span class="pln">cd </span><span class="pun">~</span><span class="pln">
git clone https</span><span class="pun">:</span><span class="com">//github.com/laravel/laravel.git laravel-app</span></pre>

<p>
	ننتقل إلى المجلّد الجديد:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_3536_68" style="">
<span class="pln">cd </span><span class="pun">~/</span><span class="pln">laravel</span><span class="pun">-</span><span class="pln">app</span></pre>

<p>
	نستخدم صورة <a href="https://hub.docker.com/_/composer" rel="external nofollow">composer</a> لمشاركة المجلدات التي نحتاجها ضمن مشروع لارافيل ويفيدنا ذلك أيضًا بتجنب أعباء تثبيت الأداة Composer عمومًا ضمن الخادم. ننفذ الأمر التالي لتحقيق ذلك:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_3536_64" style="">
<span class="pln">docker run </span><span class="pun">--</span><span class="pln">rm </span><span class="pun">-</span><span class="pln">v $</span><span class="pun">(</span><span class="pln">pwd</span><span class="pun">):/</span><span class="pln">app composer install</span></pre>

<p>
	تُنشئ التعليمة السابقة باستخدام الرايات <code>v-</code> و <code>rm–</code> حاويةً تزول تلقائيًا عند الانتهاء من عملها أي عندما ينفذ المستخدم الأمر<code>exit</code>، وتُربط بالمسار الحالي قبل إزالتها، وسينسخ هذا محتويات مسار التطبيق "laravel-app/~" إلى الحاوية، ويضمن نسخ المجلد "vendor" الذي أنشأه Composer داخل الحاوية إلى المسار الحالي. نضبط أذونات التطبيق على أنه مستخدم غير جذر non-root:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_3536_70" style="">
<span class="pln">sudo chown </span><span class="pun">-</span><span class="pln">R $USER</span><span class="pun">:</span><span class="pln">$USER </span><span class="pun">~/</span><span class="pln">laravel</span><span class="pun">-</span><span class="pln">app</span></pre>

<p>
	سيكون هذا مهمًا عند كتابة Dockerfile لصورة التطبيق الخاص بك في الخطوة 4، إذ سيسمح لك بالعمل مع شيفرة التطبيق الخاصة بك وتشغيل العمليات في الحاوية الخاصة بك مثل مستخدم غير جذر. بعد الانتهاء من كتابة شيفرة التطبيق الخاص بك، يمكنك الانتقال إلى تحديد خدماتك باستخدام دوكر كومبوز.
</p>

<h2>
	الخطوة 2- إنشاء ملف دوكر كومبوز
</h2>

<p>
	تُبسّط الأداة دوكر كومبوز عملية إعداد التهيئة الأساسية للبنية التحتية المستخدمة في مشروع تطوير معين مع الاحتفاظ بالتعديلات المختلفة المُطبقة والتي ندعو كل منها إصدارًا. ننشئ ملف "docker-compose" لإعداد تطبيق لارافيل يتضمن إنشاء البيئة اللازمة للتطبيق من خادم الويب وقاعدة البيانات والخدمات الأخرى.
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_3536_72" style="">
<span class="pln">nano </span><span class="pun">~</span><span class="str">/laravel-app/</span><span class="pln">docker</span><span class="pun">-</span><span class="pln">compose</span><span class="pun">.</span><span class="pln">yml</span></pre>

<p>
	نضبط الخدمات الثلاثة "db" و "webserver" و "app"بإضافة الشيفرة البرمجية التالية الى الملف "laravel-app/docker-compose.yml/~" مع الأخذ بالحسبان ضبط كلمات المرور جيدًا. نتأكد من وضع كلمة المرورMYSQL_ROOT_PASSWORD، بحيث تصبح هذه القيمة متغيرًا خاصًا بكامل البيئة أي ضمن خادم الويب وقاعدة البيانات وخدمات التطبيق المختلفة.
</p>

<p>
	نضيف المحتوى التالي إلى الملف "laravel-app/docker-compose.yml/~":
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_3536_74" style="">
<span class="pln">version</span><span class="pun">:</span><span class="pln"> </span><span class="str">'3'</span><span class="pln">
services</span><span class="pun">:</span><span class="pln">

  </span><span class="com">#PHP Service</span><span class="pln">
  app</span><span class="pun">:</span><span class="pln">
    build</span><span class="pun">:</span><span class="pln">
      context</span><span class="pun">:</span><span class="pln"> </span><span class="pun">.</span><span class="pln">
      dockerfile</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Dockerfile</span><span class="pln">
    image</span><span class="pun">:</span><span class="pln"> digitalocean</span><span class="pun">.</span><span class="pln">com</span><span class="pun">/</span><span class="pln">php
    container_name</span><span class="pun">:</span><span class="pln"> app
    restart</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">unless</span><span class="pun">-</span><span class="pln">stopped
    tty</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pln">
    environment</span><span class="pun">:</span><span class="pln">
      SERVICE_NAME</span><span class="pun">:</span><span class="pln"> app
      SERVICE_TAGS</span><span class="pun">:</span><span class="pln"> dev
    working_dir</span><span class="pun">:</span><span class="pln"> </span><span class="str">/var/</span><span class="pln">www
    networks</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> app</span><span class="pun">-</span><span class="pln">network

  </span><span class="com">#Nginx Service</span><span class="pln">
  webserver</span><span class="pun">:</span><span class="pln">
    image</span><span class="pun">:</span><span class="pln"> nginx</span><span class="pun">:</span><span class="pln">alpine
    container_name</span><span class="pun">:</span><span class="pln"> webserver
    restart</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">unless</span><span class="pun">-</span><span class="pln">stopped
    tty</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pln">
    ports</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> </span><span class="str">"80:80"</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> </span><span class="str">"443:443"</span><span class="pln">
    networks</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> app</span><span class="pun">-</span><span class="pln">network

  </span><span class="com">#MySQL Service</span><span class="pln">
  db</span><span class="pun">:</span><span class="pln">
    image</span><span class="pun">:</span><span class="pln"> mysql</span><span class="pun">:</span><span class="lit">5.7</span><span class="pun">.</span><span class="lit">22</span><span class="pln">
    container_name</span><span class="pun">:</span><span class="pln"> db
    restart</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">unless</span><span class="pun">-</span><span class="pln">stopped
    tty</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pln">
    ports</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> </span><span class="str">"3306:3306"</span><span class="pln">
    environment</span><span class="pun">:</span><span class="pln">
      MYSQL_DATABASE</span><span class="pun">:</span><span class="pln"> laravel
      MYSQL_ROOT_PASSWORD</span><span class="pun">:</span><span class="pln"> your_mysql_root_password
      SERVICE_TAGS</span><span class="pun">:</span><span class="pln"> dev
      SERVICE_NAME</span><span class="pun">:</span><span class="pln"> mysql
    networks</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> app</span><span class="pun">-</span><span class="pln">network

</span><span class="com">#Docker Networks</span><span class="pln">
networks</span><span class="pun">:</span><span class="pln">
  app</span><span class="pun">-</span><span class="pln">network</span><span class="pun">:</span><span class="pln">
    driver</span><span class="pun">:</span><span class="pln"> bridge</span></pre>

<p>
	يتضمن هذا الملف تعريف الخدمات التالية:
</p>

<ul>
<li>
		"app": تحدد هذه الخدمة صورة دوكر التي ستُستخدم وتضبط مجلد العمل <code>working_dir</code> في الحاوية ليصبح <code>var/www/</code>.
	</li>
	<li>
		"webserver": تحدد صورة <a href="https://hub.docker.com/_/nginx/" rel="external nofollow">nginx:alpine</a> وتعيّن المنفذين <code>80</code> و <code>443</code>.
	</li>
	<li>
		"db": تنشئ حاوية انطلاقًا من صورة <a href="https://hub.docker.com/_/mysql/" rel="external nofollow">mysql:5.7.22</a> وتعرّف بعض متغيرات البيئة الأخرى، مثل اسم قاعدة البيانات وكلمة المرور المستخدمة والتي يجب أن تكون مناسبة. تحدد هذه الخدمة أيضًا المنافذ المستخدمة، إذ تربط المنفذ <code>3306</code> للمضيف إلى المنفذ <code>3306</code> للحاوية.
	</li>
</ul>
<p>
	تحدد خاصية <code>container_name</code> اسمًا للحاوية يتوافق مع اسم الخدمة، وإذا لم تحدد هذه الخاصية، يعين دوكر اسمًا لكل حاوية من خلال الجمع بين اسم شخص مشهور تاريخيًا وكلمة عشوائية مفصولة بشرطة سفلية.
</p>

<p>
	لتسهيل الاتصال بين الحاويات، تُوصل الخدمات بشبكة جسرية تسمى <code>app-network</code>، إذ تستخدم هذه الشبكة جسرًا برمجيًا Software Bridge يسمح للحاويات المتصلة بنفس الشبكة التواصل مع بعضها بعضًا. يثبّت برنامج تشغيل الجسر Bridge driver آليًا قواعد الاتصال الشبكي على الجهاز المضيف بحيث يضمن عزل الحاويات الموجودة على الشبكات الجسرية المختلفة عن بعضها بعضًا ما لم يرغب المستخدم بربطها معًا. يزيد ذلك من أمان التطبيق ويضمن تواصل الخدمات ذات الصلة فقط مع بعضها بعضًا، وبذلك تستخدم خدمات التطبيقات الأمامية شبكة "frontend" وتستخدم خدمات الواجهة الخلفية شبكة "backend".
</p>

<h2>
	الخطوة 3- ديمومة البيانات
</h2>

<p>
	يتضمن دوكر عدة مزايا تضمن احتفاظ أو ديمومة البيانات Data Persistence، ففي تطبيقنا نستخدم <a href="https://docs.docker.com/storage/volumes/" rel="external nofollow">وحدات التخزين Volumes</a> و<a href="https://docs.docker.com/storage/bind-mounts/" rel="external nofollow">حوامل الربط bind mounts</a> لضمان احتفاظ البيانات ضمن قاعدة البيانات وملفات التطبيق والتهيئة العامة؛ إذ توفر وحدات التخزين المرونة <a href="https://academy.hsoub.com/apps/general/%D8%A7%D9%84%D9%86%D8%B3%D8%AE-%D8%A7%D9%84%D8%A7%D8%AD%D8%AA%D9%8A%D8%A7%D8%B7%D9%8A-%D9%88%D8%AD%D9%81%D8%B8-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D9%81%D9%8A-%D8%A7%D9%84%D8%B9%D8%A7%D9%84%D9%85-%D8%A7%D9%84%D8%B1%D9%82%D9%85%D9%8A-r375/" rel="">للنسخ الاحتياطي</a> والاستمرار الى ما بعد دورة حياة الحاوية؛ وتسهل حوامل الربط تغيير التعليمات البرمجية أثناء التطوير، مما يزامن إجراء التغييرات على ملفات المضيف والمجلدات في الحاوية لتنعكس هذه التغييرات فور اعتمادها على التطبيق.
</p>

<p>
	<strong>تحذير</strong>: تتيح حوامل الربط إمكانية تغيير نظام ملفات المضيف من خلال العمليات التي تعمل في الحاوية، بما في ذلك إنشاء أو تعديل أو حذف ملفات النظام أو المجلدات المهمة. على الرغم من الفائدة الكبيرة لهذه الميزة إلا أنّ لها انعكاسات أمنية يمكن أن تؤثر على العمليات التي لا تتعلق بدوكر على النظام المضيف لذلك يتوجب استخدام حوامل الربط بعناية.
</p>

<p>
	ننشئ وحدة تخزين تدعى <code>dbdata</code> في خدمة <code>db</code> من أجل احتفاظ قاعدة البيانات MySQL بوضع البيانات التالية ضمن ملف <code>docker-compose.yml</code>
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_3536_27" style="">
<span class="pun">...</span><span class="pln">
</span><span class="com">#MySQL Service</span><span class="pln">
db</span><span class="pun">:</span><span class="pln">
  </span><span class="pun">...</span><span class="pln">
    volumes</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> dbdata</span><span class="pun">:</span><span class="str">/var/</span><span class="pln">lib</span><span class="pun">/</span><span class="pln">mysql
    networks</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> app</span><span class="pun">-</span><span class="pln">network
  </span><span class="pun">...</span></pre>

<p>
	تضمن وحدة التخزين <code>dbdata</code> الاحتفاظ بالمحتوى في المجلد <code>‎/var/lib/mysql</code> وبذلك يمكن إيقاف وإعادة تشغيل خدمة <code>db</code> دون فقدان البيانات. نضيف في نهاية ملف التعريف التالي لوحدة التخزين <code>dbdata</code>:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_3536_29" style="">
<span class="pun">...</span><span class="pln">
</span><span class="com">#Volumes</span><span class="pln">
volumes</span><span class="pun">:</span><span class="pln">
  dbdata</span><span class="pun">:</span><span class="pln">
    driver</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">local</span></pre>

<p>
	نضيف حامل الربط إلى خدمة <code>db</code> لملفات تهيئة قاعدة البيانات MySQL التي ننشئها لاحقًا:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_3536_31" style="">
<span class="pun">...</span><span class="pln">
</span><span class="com">#MySQL Service</span><span class="pln">
db</span><span class="pun">:</span><span class="pln">
  </span><span class="pun">...</span><span class="pln">
    volumes</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> dbdata</span><span class="pun">:</span><span class="str">/var/</span><span class="pln">lib</span><span class="pun">/</span><span class="pln">mysql
      </span><span class="pun">-</span><span class="pln"> </span><span class="pun">./</span><span class="pln">mysql</span><span class="pun">/</span><span class="kwd">my</span><span class="pun">.</span><span class="pln">cnf</span><span class="pun">:</span><span class="str">/etc/</span><span class="pln">mysql</span><span class="pun">/</span><span class="kwd">my</span><span class="pun">.</span><span class="pln">cnf
  </span><span class="pun">...</span></pre>

<p>
	يربط حامل الربط الملف "‎~/laravel-app/mysql/my.cnf" بالملف "‎/etc/mysql/my.cnf" داخل الحاوية، ومن ثم نضيف حوامل الربط إلى خدمة <code>webserver</code>، إذ توجد خدمتان، تعود الأولى للشيفرة البرمجية للتطبيق والأخرى لتعريف تهيئة خادم Nginx الذي سننشئه في الخطوة 6:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_3536_33" style="">
<span class="com">#Nginx Service</span><span class="pln">
webserver</span><span class="pun">:</span><span class="pln">
  </span><span class="pun">...</span><span class="pln">
  volumes</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> </span><span class="pun">./:</span><span class="str">/var/</span><span class="pln">www
      </span><span class="pun">-</span><span class="pln"> </span><span class="pun">./</span><span class="pln">nginx</span><span class="pun">/</span><span class="pln">conf</span><span class="pun">.</span><span class="pln">d</span><span class="pun">/:</span><span class="str">/etc/</span><span class="pln">nginx</span><span class="pun">/</span><span class="pln">conf</span><span class="pun">.</span><span class="pln">d</span><span class="pun">/</span><span class="pln">
  networks</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> app</span><span class="pun">-</span><span class="pln">network</span></pre>

<p>
	يربط حامل الربط الأول الشيفرة البرمجية للتطبيق في مجلد التطبيق "‎~/laravel-app" بالمجلد "‎/var/www" داخل الحاوية، ويحمّل ملف التهيئة الذي نضيفه إلى المجلد "‎~/laravel-app/nginx/conf.d/‎" إلى المجلد "/etc/nginx/conf.d/" في الحاوية، وبذلك نضيف أو نعدل محتويات مجلد التهيئة حسب الحاجة. نضيف حوامل الربط إلى الخدمة <code>app</code> على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_3536_35" style="">
<span class="com">#PHP Service</span><span class="pln">
app</span><span class="pun">:</span><span class="pln">
  </span><span class="pun">...</span><span class="pln">
  volumes</span><span class="pun">:</span><span class="pln">
       </span><span class="pun">-</span><span class="pln"> </span><span class="pun">./:</span><span class="str">/var/</span><span class="pln">www
       </span><span class="pun">-</span><span class="pln"> </span><span class="pun">./</span><span class="pln">php</span><span class="pun">/</span><span class="kwd">local</span><span class="pun">.</span><span class="pln">ini</span><span class="pun">:</span><span class="str">/usr/</span><span class="kwd">local</span><span class="pun">/</span><span class="pln">etc</span><span class="pun">/</span><span class="pln">php</span><span class="pun">/</span><span class="pln">conf</span><span class="pun">.</span><span class="pln">d</span><span class="pun">/</span><span class="kwd">local</span><span class="pun">.</span><span class="pln">ini
  networks</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> app</span><span class="pun">-</span><span class="pln">network</span></pre>

<p>
	تربط الخدمة <code>app</code>مجلد التطبيق "laravel-app/~" الذي يحتوي على الشيفرة البرمجية للتطبيق بالمجلد "var/www" في الحاوية لتسريع عملية التطوير، نظرًا لأن أية تغييرات تحصل على مجلد التطبيق المحلي تنعكس على الفور داخل الحاوية. تربط الخدمة أيضًا ملف تهيئة خادم PHP الموجود في المسار "laravel-app/php/local.ini/~" بالملف "usr/local/etc/php/conf.d/local.ini/" داخل الحاوية. يصبح محتوى الملف "docker-compose.yml"على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_3536_37" style="">
<span class="pln">version</span><span class="pun">:</span><span class="pln"> </span><span class="str">'3'</span><span class="pln">
services</span><span class="pun">:</span><span class="pln">

  </span><span class="com">#PHP Service</span><span class="pln">
  app</span><span class="pun">:</span><span class="pln">
    build</span><span class="pun">:</span><span class="pln">
      context</span><span class="pun">:</span><span class="pln"> </span><span class="pun">.</span><span class="pln">
      dockerfile</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Dockerfile</span><span class="pln">
    image</span><span class="pun">:</span><span class="pln"> digitalocean</span><span class="pun">.</span><span class="pln">com</span><span class="pun">/</span><span class="pln">php
    container_name</span><span class="pun">:</span><span class="pln"> app
    restart</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">unless</span><span class="pun">-</span><span class="pln">stopped
    tty</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pln">
    environment</span><span class="pun">:</span><span class="pln">
      SERVICE_NAME</span><span class="pun">:</span><span class="pln"> app
      SERVICE_TAGS</span><span class="pun">:</span><span class="pln"> dev
    working_dir</span><span class="pun">:</span><span class="pln"> </span><span class="str">/var/</span><span class="pln">www
    volumes</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> </span><span class="pun">./:</span><span class="str">/var/</span><span class="pln">www
      </span><span class="pun">-</span><span class="pln"> </span><span class="pun">./</span><span class="pln">php</span><span class="pun">/</span><span class="kwd">local</span><span class="pun">.</span><span class="pln">ini</span><span class="pun">:</span><span class="str">/usr/</span><span class="kwd">local</span><span class="pun">/</span><span class="pln">etc</span><span class="pun">/</span><span class="pln">php</span><span class="pun">/</span><span class="pln">conf</span><span class="pun">.</span><span class="pln">d</span><span class="pun">/</span><span class="kwd">local</span><span class="pun">.</span><span class="pln">ini
    networks</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> app</span><span class="pun">-</span><span class="pln">network

  </span><span class="com">#Nginx Service</span><span class="pln">
  webserver</span><span class="pun">:</span><span class="pln">
    image</span><span class="pun">:</span><span class="pln"> nginx</span><span class="pun">:</span><span class="pln">alpine
    container_name</span><span class="pun">:</span><span class="pln"> webserver
    restart</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">unless</span><span class="pun">-</span><span class="pln">stopped
    tty</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pln">
    ports</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> </span><span class="str">"80:80"</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> </span><span class="str">"443:443"</span><span class="pln">
    volumes</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> </span><span class="pun">./:</span><span class="str">/var/</span><span class="pln">www
      </span><span class="pun">-</span><span class="pln"> </span><span class="pun">./</span><span class="pln">nginx</span><span class="pun">/</span><span class="pln">conf</span><span class="pun">.</span><span class="pln">d</span><span class="pun">/:</span><span class="str">/etc/</span><span class="pln">nginx</span><span class="pun">/</span><span class="pln">conf</span><span class="pun">.</span><span class="pln">d</span><span class="pun">/</span><span class="pln">
    networks</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> app</span><span class="pun">-</span><span class="pln">network

  </span><span class="com">#MySQL Service</span><span class="pln">
  db</span><span class="pun">:</span><span class="pln">
    image</span><span class="pun">:</span><span class="pln"> mysql</span><span class="pun">:</span><span class="lit">5.7</span><span class="pun">.</span><span class="lit">22</span><span class="pln">
    container_name</span><span class="pun">:</span><span class="pln"> db
    restart</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">unless</span><span class="pun">-</span><span class="pln">stopped
    tty</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pln">
    ports</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> </span><span class="str">"3306:3306"</span><span class="pln">
    environment</span><span class="pun">:</span><span class="pln">
      MYSQL_DATABASE</span><span class="pun">:</span><span class="pln"> laravel
      MYSQL_ROOT_PASSWORD</span><span class="pun">:</span><span class="pln"> your_mysql_root_password
      SERVICE_TAGS</span><span class="pun">:</span><span class="pln"> dev
      SERVICE_NAME</span><span class="pun">:</span><span class="pln"> mysql
    volumes</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> dbdata</span><span class="pun">:</span><span class="str">/var/</span><span class="pln">lib</span><span class="pun">/</span><span class="pln">mysql</span><span class="pun">/</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> </span><span class="pun">./</span><span class="pln">mysql</span><span class="pun">/</span><span class="kwd">my</span><span class="pun">.</span><span class="pln">cnf</span><span class="pun">:</span><span class="str">/etc/</span><span class="pln">mysql</span><span class="pun">/</span><span class="kwd">my</span><span class="pun">.</span><span class="pln">cnf
    networks</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> app</span><span class="pun">-</span><span class="pln">network

</span><span class="com">#Docker Networks</span><span class="pln">
networks</span><span class="pun">:</span><span class="pln">
  app</span><span class="pun">-</span><span class="pln">network</span><span class="pun">:</span><span class="pln">
    driver</span><span class="pun">:</span><span class="pln"> bridge
</span><span class="com">#Volumes</span><span class="pln">
volumes</span><span class="pun">:</span><span class="pln">
  dbdata</span><span class="pun">:</span><span class="pln">
    driver</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">local</span></pre>

<p>
	نحفظ الملف بعد الانتهاء من إجراء التعديلات السابقة. نستطيع الآن باستخدام الأداة دوكر كومبوز بناء الصورة المخصًصة التي أنشأناها لتطبيقنا
</p>

<h2>
	الخطوة 4- إنشاء ملف Dockerfile
</h2>

<p>
	يسمح دوكر لك بتخصيص البيئة داخل الحاويات الفردية باستخدام ملف Dockerfile، الذي يمكّننا من إنشاء صور مخصصة يمكننا استخدامها لتثبيت البرنامج الذي يتطلبه التطبيق وضبط الإعدادات بناءً على المتطلبات. يمكن دفع الصور المخصصة التي ننشئها إلى <a href="https://hub.docker.com/" rel="external nofollow">Docker Hub</a> أو أي سجل خاص آخر. نضع الملف Dockerfile ضمن "laravel-app/~"، ولإنشائه ننفّذ الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_3536_80" style="">
<span class="pln">nano </span><span class="pun">~</span><span class="str">/laravel-app/</span><span class="typ">Dockerfile</span></pre>

<p>
	يحدد ملف دوكر هذا الصورة الأساسية ويحدد الأوامر والإرشادات اللازمة لبناء صورة تطبيق لارافيل. نضيف الشيفرة البرمجية التالية إلى الملف:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_3536_82" style="">
<span class="pln">FROM php</span><span class="pun">:</span><span class="lit">7.2</span><span class="pun">-</span><span class="pln">fpm

</span><span class="com"># Copy composer.lock and composer.json</span><span class="pln">
COPY composer</span><span class="pun">.</span><span class="kwd">lock</span><span class="pln"> composer</span><span class="pun">.</span><span class="pln">json </span><span class="pun">/</span><span class="kwd">var</span><span class="pun">/</span><span class="pln">www</span><span class="pun">/</span><span class="pln">

</span><span class="com"># Set working directory</span><span class="pln">
WORKDIR </span><span class="pun">/</span><span class="kwd">var</span><span class="pun">/</span><span class="pln">www

</span><span class="com"># Install dependencies</span><span class="pln">
RUN apt</span><span class="pun">-</span><span class="kwd">get</span><span class="pln"> update </span><span class="pun">&amp;&amp;</span><span class="pln"> apt</span><span class="pun">-</span><span class="kwd">get</span><span class="pln"> install </span><span class="pun">-</span><span class="pln">y \
    build</span><span class="pun">-</span><span class="pln">essential \
    libpng</span><span class="pun">-</span><span class="pln">dev \
    libjpeg62</span><span class="pun">-</span><span class="pln">turbo</span><span class="pun">-</span><span class="pln">dev \
    libfreetype6</span><span class="pun">-</span><span class="pln">dev \
    locales \
    zip \
    jpegoptim optipng pngquant gifsicle \
    vim \
    unzip \
    git \
    curl

</span><span class="com"># Clear cache</span><span class="pln">
RUN apt</span><span class="pun">-</span><span class="kwd">get</span><span class="pln"> clean </span><span class="pun">&amp;&amp;</span><span class="pln"> rm </span><span class="pun">-</span><span class="pln">rf </span><span class="pun">/</span><span class="kwd">var</span><span class="pun">/</span><span class="pln">lib</span><span class="pun">/</span><span class="pln">apt</span><span class="pun">/</span><span class="pln">lists</span><span class="com">/*

# Install extensions
RUN docker-php-ext-install pdo_mysql mbstring zip exif pcntl
RUN docker-php-ext-configure gd --with-gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ --with-png-dir=/usr/include/
RUN docker-php-ext-install gd

# Install composer
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

# Add user for laravel application
RUN groupadd -g 1000 www
RUN useradd -u 1000 -ms /bin/bash -g www www

# Copy existing application directory contents
COPY . /var/www

# Copy existing application directory permissions
COPY --chown=www:www . /var/www

# Change current user to www
USER www

# Expose port 9000 and start php-fpm server
EXPOSE 9000
CMD ["php-fpm"]</span></pre>

<p>
	ينشئ ملف Dockerfile صورةً جديدةً على أساس صورة دوكر <code>php:7.2-fpm</code> المبنية على نظام دبيان Debian، ويثبّت أداة الإدارة <a href="https://php-fpm.org/" rel="external nofollow">PHP-FPM</a> من أجل التحكم بتطبيق PHP FastCGI، كما يثبت حزم لارافيل التالية: <code>mcrypt</code> و <code>pdo_mysql</code> و <code>mbstring</code> و <code>imagick</code> مع <code>composer</code>.
</p>

<p>
	يوجه أمر التوجيه <code>RUN</code> الأوامر لتحديث الإعدادات وتثبيتها وتكوينها داخل الحاوية، بما في ذلك إنشاء مستخدم مخصص ومجموعة تسمّى <strong>www</strong>. تحدد تعليمة <code>WORKDIR</code> المجلد "‎/var/www" على أنه مجلد العمل الأساسي للتطبيق.
</p>

<p>
	يخفف إنشاء مستخدم ومجموعة بأذونات مقيدة الخطر الذي ينتج عن الثغرات الأمنية الناتجة عن تشغيل حاويات دوكر. عندما تبدأ الحاوية للعمل فإنها تستخدم حساب الجذر افتراضيًا ما لم يُحدّد مستخدم آخر في ملف الإعداد الخاص بالصورة. يمتلك هذا الحساب الأذونات المطلقة على الجهاز المضيف والحاويات معًا. بدلاً من تشغيل هذه الحاوية باستخدام المستخدم الجذر، ننشئ مستخدم<strong>www</strong> يمتلك أذونات القراءة والكتابة على المجلد "var/www/" بفضل تعليمات <code>COPY</code>التي نستخدمها مع العلامة <code>chown--</code> لنسخ أذونات مجلد التطبيق. يعرض الأمر <code>EXPOSE</code> المنفذ <code>9000</code> في الحاوية لخادم "php-fpm" ويحدد<code>CMD</code> الأمر الذي يجب تشغيله بمجرد إنشاء الحاوية، وهو هنا "php-fpm"، الذي سيشغّل الخادم.
</p>

<h2>
	الخطوة 5- إعداد خادم PHP
</h2>

<p>
	نُعد خدمة PHP لتعمل مثل معالج PHP للطلبات الواردة من Nginx. ولضبط ذلك ننشئ ملف "local.ini" داخل مجلد "php" الذي ربطناه بالملف "usr/local/etc/php/conf.d/local.ini/" داخل الحاوية في خطوة سابقة، إذ يسمح هذا الملف بتجاوز ملف "php.ini" الافتراضي الذي يفحصه خادم PHP عندما نبدأ بالعمل. ننشئ مجلد <code>php</code> بالأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_3536_44" style="">
<span class="pln">mkdir </span><span class="pun">~</span><span class="str">/laravel-app/</span><span class="pln">php</span></pre>

<p>
	نحرر الملف"local.ini" باستخدام محرر النصوص المفضل وبحالة المحرر نانو nano ننفذ الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_3536_46" style="">
<span class="pln">nano </span><span class="pun">~</span><span class="str">/laravel-app/</span><span class="pln">php</span><span class="pun">/</span><span class="kwd">local</span><span class="pun">.</span><span class="pln">ini</span></pre>

<p>
	نلصق داخل الملف الأوامر التالية لتحديد أقصى حجم لتحميل الملفات:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_3536_48" style="">
<span class="pln">upload_max_filesize</span><span class="pun">=</span><span class="lit">40M</span><span class="pln">
post_max_size</span><span class="pun">=</span><span class="lit">40M</span></pre>

<p>
	تعيّن كل من الخاصتين <code>upload_max_filesize</code> و <code>post_max_size</code> الحجم الأقصى المسموح به لرفع الملفات إلى الخادم، ويوضح تعديل هاتين الخاصتين إمكانية إعداد الملف"php.ini" انطلاقًا من تعديل الملف "local.ini"، ويمكن إضافة أية إعدادات خاصة بخادم PHP نرغب بتطبيقها ضمن هذا الملف. نحفظ الملف بعد إجراء التعديلات السابقة ومن ثم نغلق محرر النصوص، وبهذا ينتهي إعداد خادم PHP وننتقل لإعداد خادم Nginx.
</p>

<h2>
	الخطوة 6- إعداد خادم Nginx
</h2>

<p>
	يسمح إعداد خدمة PHP بتعديل خدمة Nginx لاستخدام PHP-FPM مثل خادم FastCGI لتقديم خدمة المحتوى الديناميكي، إذ يعتمد خادم FastCGI على بروتوكول ثنائي binary protocol لربط البرامج التفاعلية بخادم الويب. ننشئ ملف "app.conf" مع ضبط الخدمة في المجلد <code>/laravel-app/nginx/conf.d/~</code> من أجل إعداد Nginx. ننشئ المجلد "‎/nginx/conf.d" بتنفيذ الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_3536_50" style="">
<span class="pln">mkdir </span><span class="pun">-</span><span class="pln">p </span><span class="pun">~</span><span class="str">/laravel-app/</span><span class="pln">nginx</span><span class="pun">/</span><span class="pln">conf</span><span class="pun">.</span><span class="pln">d</span></pre>

<p>
	ننشئ بعدها الملف "app.conf" بتنفيذ الأمر:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_3536_52" style="">
<span class="pln">nano </span><span class="pun">~</span><span class="str">/laravel-app/</span><span class="pln">nginx</span><span class="pun">/</span><span class="pln">conf</span><span class="pun">.</span><span class="pln">d</span><span class="pun">/</span><span class="pln">app</span><span class="pun">.</span><span class="pln">conf</span></pre>

<p>
	نضيف المحتوى التالي لإعداد خادم Nginx:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_3536_54" style="">
<span class="pln">server </span><span class="pun">{</span><span class="pln">
    listen </span><span class="lit">80</span><span class="pun">;</span><span class="pln">
    index index</span><span class="pun">.</span><span class="pln">php index</span><span class="pun">.</span><span class="pln">html</span><span class="pun">;</span><span class="pln">
    error_log  </span><span class="pun">/</span><span class="kwd">var</span><span class="pun">/</span><span class="pln">log</span><span class="pun">/</span><span class="pln">nginx</span><span class="pun">/</span><span class="pln">error</span><span class="pun">.</span><span class="pln">log</span><span class="pun">;</span><span class="pln">
    access_log </span><span class="pun">/</span><span class="kwd">var</span><span class="pun">/</span><span class="pln">log</span><span class="pun">/</span><span class="pln">nginx</span><span class="pun">/</span><span class="pln">access</span><span class="pun">.</span><span class="pln">log</span><span class="pun">;</span><span class="pln">
    root </span><span class="pun">/</span><span class="kwd">var</span><span class="pun">/</span><span class="pln">www</span><span class="pun">/</span><span class="kwd">public</span><span class="pun">;</span><span class="pln">
    location </span><span class="pun">~</span><span class="pln"> \.php$ </span><span class="pun">{</span><span class="pln">
        try_files $uri </span><span class="pun">=</span><span class="lit">404</span><span class="pun">;</span><span class="pln">
        fastcgi_split_path_info </span><span class="pun">^(.+</span><span class="pln">\.php</span><span class="pun">)(/.+)</span><span class="pln">$</span><span class="pun">;</span><span class="pln">
        fastcgi_pass app</span><span class="pun">:</span><span class="lit">9000</span><span class="pun">;</span><span class="pln">
        fastcgi_index index</span><span class="pun">.</span><span class="pln">php</span><span class="pun">;</span><span class="pln">
        include fastcgi_params</span><span class="pun">;</span><span class="pln">
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name</span><span class="pun">;</span><span class="pln">
        fastcgi_param PATH_INFO $fastcgi_path_info</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    location </span><span class="pun">/</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        try_files $uri $uri</span><span class="pun">/</span><span class="pln"> </span><span class="pun">/</span><span class="pln">index</span><span class="pun">.</span><span class="pln">php</span><span class="pun">?</span><span class="pln">$query_string</span><span class="pun">;</span><span class="pln">
        gzip_static on</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	يتضمن إعداد <a href="https://academy.hsoub.com/devops/servers/web/nginx/%D9%81%D9%87%D9%85-%D8%A2%D9%84%D9%8A%D8%A9-%D8%B9%D9%85%D9%84-%D8%AE%D9%88%D8%A7%D8%B1%D8%B2%D9%85%D9%8A%D9%91%D8%A9-%D8%A7%D9%84%D8%A7%D8%AE%D8%AA%D9%8A%D9%91%D8%A7%D8%B1-%D9%81%D9%8A-%D9%83%D8%AA%D9%84%D8%A9-location-%D9%84%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF%D8%A7%D8%AA-%D8%AE%D8%A7%D8%AF%D9%88%D9%85-nginx-r68/" rel="">كتلة الخادم</a> الموجهات directives الضرورية لإعداد خادم الويب Nginx إذ تدل هذه الموجهات إلى ما يلي:
</p>

<ul>
<li>
		<code>listen</code>: يحدد هذا الموجه المنفذ الذي يستمع الخادم إليه من أجل استقبال الطلبات.
	</li>
	<li>
		<code>error_log</code> و <code>access_log</code>: تحدد هذه الموجّهات الملفات لكتابة السجلات المتعلقة بالنفاذ للخادم والأخطاء التي تحدث.
	</li>
	<li>
		<code>root</code>: يعيّن هذا الموجه مسار المجلد الجذر root، ويشكّل المسار الكامل لأي ملف مطلوب على نظام الملفات المحلي.
	</li>
</ul>
<p>
	يحدد الموجه <code>fastcgi_pass</code> أن الخدمة <code>app</code> تستمع إلى مقبس TCP على المنفذ "9000" وهذا يجعل خادم PHP-FPM يستمع عبر الشبكة بدلاً من مقبس Unix. على الرغم من تفوق مقبس Unix من حيث السرعة بصورةٍ طفيفة على مقبس TCP، إلا أنه لا يحتوي على بروتوكول شبكة وبالتالي يتخطى مكدس الشبكة.
</p>

<p>
	يناسب مقبس Unix الحالات التي يتواجد فيها أكثر من مضيف على جهاز واحد، ولكن في الحالات التي يكون لدينا خدمات تعمل على مجموعة موزعة من المضيفات، فيوفّر مقبس TCP ميزة السماح لك بالاتصال بالخدمات الموزعة. نظرًا لأن حاوية التطبيق app الخاصة تعمل على مضيف مختلف من حاوية webserver، يكون مقبس TCP أكثر ملاءمةً للضبط الذي نستخدمه.
</p>

<h2>
	الخطوة 7- إعداد قاعدة بيانات MYSQL
</h2>

<p>
	ننشئ ملف "my.cnf" في المجلد "mysql" الذي ربطناه بالملف "‎/etc/mysql/my.cnf" داخل الحاوية بالخطوة 2. يسمح حامل الربط هذا بتجاوز ملف "my.cnf" الأساسي عند الطلب واستخدام الملف الذي نجري التعديلات عليه. نضيف الإعدادات التالية إلى ملف "my.cnf" لتفعيل سجل الاستعلام العام وتحديد ملف السجل. ننشئ أولًا المجلد "mysql" بتنفيذ الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_3536_89" style="">
<span class="pln">mkdir </span><span class="pun">~</span><span class="str">/laravel-app/</span><span class="pln">mysql</span></pre>

<p>
	ومن ثم ننشئ الملف "my.cnf" بتنفيذ الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_3536_91" style="">
<span class="pln">nano </span><span class="pun">~</span><span class="str">/laravel-app/</span><span class="pln">mysql</span><span class="pun">/</span><span class="kwd">my</span><span class="pun">.</span><span class="pln">cnf</span></pre>

<p>
	نضيف المحتوى التالي إلى الملف:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_3536_93" style="">
<span class="pun">[</span><span class="pln">mysqld</span><span class="pun">]</span><span class="pln">
general_log </span><span class="pun">=</span><span class="pln"> </span><span class="lit">1</span><span class="pln">
general_log_file </span><span class="pun">=</span><span class="pln"> </span><span class="str">/var/</span><span class="pln">lib</span><span class="pun">/</span><span class="pln">mysql</span><span class="pun">/</span><span class="pln">general</span><span class="pun">.</span><span class="pln">log</span></pre>

<p>
	نفعّل سجل الاستعلام بوضع القيمة <code>1</code> للخيار <code>general_log</code> ونحدد المكان الذي نريد حفظ السجل به بتحديد قيمة الخيار <code>general_log_file</code>.
</p>

<h2>
	الخطوة 8- تعديل إعدادات البيئة وتشغيل الحاويات
</h2>

<p>
	حددنا جميع الخدمات في ملف "docker-compose" وأنشأنا ملفات الضبط لهذه الخدمات وحان الوقت لتشغيل التطبيق. ننشئ نسخةً من الملف "env.example." الذي يولّده لارافيل افتراضيًا ونسمّي النسخة التي نعمل عليها حاليًا "env." بتنفيذ الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_3536_95" style="">
<span class="pln">cp </span><span class="pun">.</span><span class="pln">env</span><span class="pun">.</span><span class="pln">example </span><span class="pun">.</span><span class="pln">env</span></pre>

<p>
	نعدل على الملف الذي جرى نسخه لنضع البيانات الخاصة بحاوية تطبيقنا باستخدام محرر النصوص المفضل:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_3536_97" style="">
<span class="pln">nano </span><span class="pun">.</span><span class="pln">env</span></pre>

<p>
	نبحث عن الكتلة التي تحدد الاتصال مع قاعدة البيانات <code>DB_CONNECTION</code> ونعدل ضمنها الحقول التالية:
</p>

<ul>
<li>
		<code>DB_HOST</code>: عنوان حاوية قاعدة بيانات <code>db</code>.
	</li>
	<li>
		<code>DB_DATABASE</code>: اسم قاعدة بيانات لارافيل.
	</li>
	<li>
		<code>DB_USERNAME</code>: اسم المستخدم الذي نستخدمه <a href="https://academy.hsoub.com/programming/sql/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%B9%D9%86-%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-r584/" rel="">لقاعدة البيانات</a>، نستخدم "laraveluser" ويمكن استخدام أي اسم آخر نرغب به.
	</li>
	<li>
		<code>DB_PASSWORD</code> كلمة المرور للاتصال بقاعدة البيانات.
	</li>
</ul>
<p>
	ويكون ملف "env." بالشكل التالي:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_3536_109" style="">
<span class="pln">DB_CONNECTION</span><span class="pun">=</span><span class="pln">mysql
DB_HOST</span><span class="pun">=</span><span class="pln">db
DB_PORT</span><span class="pun">=</span><span class="lit">3306</span><span class="pln">
DB_DATABASE</span><span class="pun">=</span><span class="pln">laravel
DB_USERNAME</span><span class="pun">=</span><span class="pln">laraveluser
DB_PASSWORD</span><span class="pun">=</span><span class="pln">your_laravel_db_password</span></pre>

<p>
	احفظ التغييرات وأغلق المحرر.
</p>

<p>
	ننفذ الآن أمر بدء تشغيل جميع الحاويات وإنشاء وحدات التخزين وإعداد الشبكات وتوصيلها:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_3536_107" style="">
<span class="pln">docker</span><span class="pun">-</span><span class="pln">compose up </span><span class="pun">-</span><span class="pln">d</span></pre>

<p>
	تُحمّل جميع صور دوكر الضرورية عند تشغيل الأمر السابق لأول مرة، وتستغرق بعض الوقت إذا لم تكن موجودة مسبقًا على الجهاز المحلي، وبمجرد تحميل الصور وتخزينها يُنشئ compose الحاويات اللازمة. تضفي الراية <code>d-</code> الطابع الخفي على العملية وتشغّلها في الخلفية. نستعرض الحاويات التي تعمل في هذه اللحظة بتنفيذ الأمر:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_3536_112" style="">
<span class="pln">docker ps</span></pre>

<p>
	يظهر خرجٌ مشابه للخرج التالي ما لم توجد حاويات أخرى إضافية غير "app" و "webserver" و "db" ضمن البيئة:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_3536_114" style="">
<span class="pln">CONTAINER ID        NAMES               IMAGE                             STATUS              PORTS
c31b7b3251e0        db                  mysql</span><span class="pun">:</span><span class="lit">5.7</span><span class="pun">.</span><span class="lit">22</span><span class="pln">                      </span><span class="typ">Up</span><span class="pln"> </span><span class="lit">2</span><span class="pln"> seconds        </span><span class="lit">0.0</span><span class="pun">.</span><span class="lit">0.0</span><span class="pun">:</span><span class="lit">3306</span><span class="pun">-&gt;</span><span class="lit">3306</span><span class="pun">/</span><span class="pln">tcp
ed5a69704580        app                 digitalocean</span><span class="pun">.</span><span class="pln">com</span><span class="pun">/</span><span class="pln">php              </span><span class="typ">Up</span><span class="pln"> </span><span class="lit">2</span><span class="pln"> seconds        </span><span class="lit">9000</span><span class="pun">/</span><span class="pln">tcp
</span><span class="lit">5ce4ee31d7c0</span><span class="pln">        webserver           nginx</span><span class="pun">:</span><span class="pln">alpine                      </span><span class="typ">Up</span><span class="pln"> </span><span class="lit">2</span><span class="pln"> seconds        </span><span class="lit">0.0</span><span class="pun">.</span><span class="lit">0.0</span><span class="pun">:</span><span class="lit">80</span><span class="pun">-&gt;</span><span class="lit">80</span><span class="pun">/</span><span class="pln">tcp</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0.0</span><span class="pun">.</span><span class="lit">0.0</span><span class="pun">:</span><span class="lit">443</span><span class="pun">-&gt;</span><span class="lit">443</span><span class="pun">/</span><span class="pln">tc</span></pre>

<p>
	يُعد <code>CONTAINER ID</code> معرفًا فريدًا لكل حاوية ويعرض <code>NAMES</code> اسم الحاوية ويعرض <code>IMAGE</code> اسم الصورة ويوفر الحقل <code>STATUS</code> معلومات حول الحاوية فيما إذا كانت تعمل أو يُعاد تشغيلها أو متوقفة نهائيًا عن العمل. نستخدم الأمر <code>docker-compose exec</code> لتوليد مفتاح التطبيق لتطبيق لارافيل وننسخه للملف "env." ليضمن حماية جلسات المستخدم وتأمين البيانات المشفرة بتنفيذ ما يلي:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_3536_116" style="">
<span class="pln">docker</span><span class="pun">-</span><span class="pln">compose </span><span class="kwd">exec</span><span class="pln"> app php artisan key</span><span class="pun">:</span><span class="pln">generate</span></pre>

<p>
	نشغّل الأمر التالي:
</p>

<pre class="ipsCode">
docker-compose exec app php artisan config:cache
</pre>

<p>
	تُحمّل إعدادات الضبط إلى الملف "‎/var/www/bootstrap/cache/config.php" ضمن الحاوية، وبزيارة الرابط التالي "http://your<em>server</em>ip" في المتصفح نرى الصفحة التالية:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="109226" href="https://academy.hsoub.com/uploads/monthly_2022_10/laravel_home.png.cbbfc623f52553216842d55f9d60f115.png" rel=""><img alt="laravel_home" class="ipsImage ipsImage_thumbnailed" data-fileid="109226" data-unique="n802ebxyc" src="https://academy.hsoub.com/uploads/monthly_2022_10/laravel_home.thumb.png.b686fba1dc9bc8d3cde02e2fd0e2f9ea.png" style="width: 600px; height: auto;"></a>
</p>

<p>
	مع تشغيل الحاويات الخاصة بك ومعلومات الضبط في مكانها الصحيح، يمكننا الانتقال إلى تكوين معلومات المستخدم الخاصة بك لقاعدة بيانات "laravel" على حاوية "db".
</p>

<h2>
	الخطوة 9- إنشاء مستخدم لقاعدة البيانات MySQL
</h2>

<p>
	يُنشئ MySQL افتراضيًا حساب الجذر <strong>root</strong> لأغراض إدارية، إذ يتمتع هذا المستخدم بصلاحيات غير محدودة على خادم قاعدة البيانات. نتجنّب استخدام هذا الحساب عند التعامل مع قاعدة البيانات وبدلاً من ذلك ننشئ مستخدم قاعدة بيانات مخصص لقاعدة بيانات تطبيق لارافيل. ننفذ الأمر التالي في حاوية "db":
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_3536_119" style="">
<span class="pln">docker</span><span class="pun">-</span><span class="pln">compose </span><span class="kwd">exec</span><span class="pln"> db bash</span></pre>

<p>
	نسجّل دخول الى حساب الجذر الخاص بقاعدة البيانات MySQL داخل الحاوية:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_3536_121" style="">
<span class="pln">root@c31b7b3251e0</span><span class="pun">:/#</span><span class="pln"> mysql </span><span class="pun">-</span><span class="pln">u root </span><span class="pun">-</span><span class="pln">p</span></pre>

<p>
	نُدخل كلمة المرور المُعينة لحساب الجذر الخاص بقاعدة البيانات MySQL أثناء التثبيت في الملف "docker-compose". نتحقق من قاعدة البيانات "laravel" التي حددناها في الملف "docker-compose" بتنفيذ الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_3536_124" style="">
<span class="pln">mysql</span><span class="pun">&gt;</span><span class="pln"> show databases</span><span class="pun">;</span></pre>

<p>
	يظهر خرج يشابه الخرج التالي:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_3536_126" style="">
<span class="pun">+--------------------+</span><span class="pln">
</span><span class="pun">|</span><span class="pln"> </span><span class="typ">Database</span><span class="pln">           </span><span class="pun">|</span><span class="pln">
</span><span class="pun">+--------------------+</span><span class="pln">
</span><span class="pun">|</span><span class="pln"> information_schema </span><span class="pun">|</span><span class="pln">
</span><span class="pun">|</span><span class="pln"> laravel            </span><span class="pun">|</span><span class="pln">
</span><span class="pun">|</span><span class="pln"> mysql              </span><span class="pun">|</span><span class="pln">
</span><span class="pun">|</span><span class="pln"> performance_schema </span><span class="pun">|</span><span class="pln">
</span><span class="pun">|</span><span class="pln"> sys                </span><span class="pun">|</span><span class="pln">
</span><span class="pun">+--------------------+</span><span class="pln">
</span><span class="lit">5</span><span class="pln"> rows </span><span class="kwd">in</span><span class="pln"> </span><span class="kwd">set</span><span class="pln"> </span><span class="pun">(</span><span class="lit">0.00</span><span class="pln"> sec</span><span class="pun">)</span></pre>

<p>
	ننشئ حساب المستخدم الذي يُسمح له بالوصول إلى قاعدة البيانات ونسميه "laraveluser". نتأكد من تطابق الاسم وكلمة المرور مع الاسم وكلمة المرور الموجودين في الملف "env.":
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_3536_128" style="">
<span class="pln">mysql</span><span class="pun">&gt;</span><span class="pln"> GRANT ALL ON laravel</span><span class="pun">.*</span><span class="pln"> TO </span><span class="str">'laraveluser'</span><span class="pun">@</span><span class="str">'%'</span><span class="pln"> IDENTIFIED BY </span><span class="str">'your_laravel_db_password'</span><span class="pun">;</span></pre>

<p>
	نمسح الصلاحيات لنُعلم خادم MySQL بالتعديلات التي أجريناها:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_3536_130" style="">
<span class="pln">mysql</span><span class="pun">&gt;</span><span class="pln"> FLUSH PRIVILEGES</span><span class="pun">;</span></pre>

<p>
	نخرج من واجهة الأوامر التفاعلية الخاصة بقاعدة البيانات MySQL والحاوية:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_3536_132" style="">
<span class="pln">mysql</span><span class="pun">&gt;</span><span class="pln"> EXIT</span><span class="pun">;</span><span class="pln">
root@c31b7b3251e0</span><span class="pun">:/#</span><span class="pln"> </span><span class="kwd">exit</span></pre>

<h2>
	الخطوة 10- تهجير البيانات والعمل باستخدام طرفية Tinker
</h2>

<p>
	نجح إعداد التطبيق إلا أنّ قاعدة البيانات لا تتضمن أية محتوى حتى الآن. نستطيع إدخال بعض المحتوى يدويًا أو الاستفادة من تهجير قواعد البيانات لملئها بمعلومات بصورةٍ سريعة. تُهجّر البيانات باستخدام طرفية tinker، التي تُشغّل الطرفية <a href="https://psysh.org/" rel="external nofollow">PsySH</a> عند تحميل تطبيق لارافيل الأولي. تُستخدم الطرفية PsySH مثل واجهة تطوير في وقت التشغيل كما أنها تعمل مثل مصحح أخطاء تفاعلي للغة PHP. تُعد طرفية Tinker بيئة اختبار REPL خاصة لإطار العمل لارافيل. يسمح استخدام الأمر <code>tinker</code> بالتفاعل مع تطبيق لارافيل باستخدام سطر الأوامر في صدفة تفاعلية وتنفيذ الأوامر عليها مباشرةً. نختبر الاتصال بقاعدة البيانات MySQL عن طريق تشغيل أمر <code>artisan migrate</code>، الذي يُنشئ جدول تهجير في قاعدة البيانات داخل الحاوية ويتحقق ذلك بتنفيذ الأمر:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_3536_143" style="">
<span class="pln">docker</span><span class="pun">-</span><span class="pln">compose </span><span class="kwd">exec</span><span class="pln"> app php artisan migrate</span></pre>

<p>
	ويظهر الخرج الذي يؤكد نجاح التهجير بالشكل التالي:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_3536_145" style="">
<span class="typ">Migration</span><span class="pln"> table created successfully</span><span class="pun">.</span><span class="pln">
</span><span class="typ">Migrating</span><span class="pun">:</span><span class="pln"> </span><span class="lit">2014</span><span class="pln">_10_12_000000_create_users_table
</span><span class="typ">Migrated</span><span class="pun">:</span><span class="pln">  </span><span class="lit">2014</span><span class="pln">_10_12_000000_create_users_table
</span><span class="typ">Migrating</span><span class="pun">:</span><span class="pln"> </span><span class="lit">2014</span><span class="pln">_10_12_100000_create_password_resets_table
</span><span class="typ">Migrated</span><span class="pun">:</span><span class="pln">  </span><span class="lit">2014</span><span class="pln">_10_12_100000_create_password_resets_table</span></pre>

<p>
	ننفذ الاستعلام التالي للتحقق من وجود اتصال بقاعدة البيانات على نحوٍ صحيح باستخدام الأمر <code>tinker</code>:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_3536_141" style="">
<span class="pln">docker</span><span class="pun">-</span><span class="pln">compose </span><span class="kwd">exec</span><span class="pln"> app php artisan tinker</span></pre>

<p>
	اختبر اتصال MySQL عن طريق الحصول على البيانات التي هجّرناها للتو:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_3536_149" style="">
<span class="pln">\DB</span><span class="pun">::</span><span class="pln">table</span><span class="pun">(</span><span class="str">'migrations'</span><span class="pun">)-&gt;</span><span class="kwd">get</span><span class="pun">();</span></pre>

<p>
	يظهر خرج مشابه للخرج التالي مع نجاح العملية:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_3536_147" style="">
<span class="pun">=&gt;</span><span class="pln"> </span><span class="typ">Illuminate</span><span class="pln">\Support\Collection </span><span class="pun">{#</span><span class="lit">2856</span><span class="pln">
     all</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
       </span><span class="pun">{#</span><span class="lit">2862</span><span class="pln">
         </span><span class="pun">+</span><span class="str">"id"</span><span class="pun">:</span><span class="pln"> </span><span class="lit">1</span><span class="pun">,</span><span class="pln">
         </span><span class="pun">+</span><span class="str">"migration"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"2014_10_12_000000_create_users_table"</span><span class="pun">,</span><span class="pln">
         </span><span class="pun">+</span><span class="str">"batch"</span><span class="pun">:</span><span class="pln"> </span><span class="lit">1</span><span class="pun">,</span><span class="pln">
       </span><span class="pun">},</span><span class="pln">
       </span><span class="pun">{#</span><span class="lit">2865</span><span class="pln">
         </span><span class="pun">+</span><span class="str">"id"</span><span class="pun">:</span><span class="pln"> </span><span class="lit">2</span><span class="pun">,</span><span class="pln">
         </span><span class="pun">+</span><span class="str">"migration"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"2014_10_12_100000_create_password_resets_table"</span><span class="pun">,</span><span class="pln">
         </span><span class="pun">+</span><span class="str">"batch"</span><span class="pun">:</span><span class="pln"> </span><span class="lit">1</span><span class="pun">,</span><span class="pln">
       </span><span class="pun">},</span><span class="pln">
     </span><span class="pun">],</span><span class="pln">
   </span><span class="pun">}</span></pre>

<p>
	تُستخدم <code>tinker</code> للتفاعل مع قواعد البيانات المختلفة وتنفيذ التجارب على النماذج والخدمات المرتبطة بها.
</p>

<h2>
	الخاتمة
</h2>

<p>
	أنشأنا في هذا المقال تطبيقًا برمجيًا باستخدام مكدس LEMP يعمل على الخادم المحلي واختبرناه من خلال الوصول إلى صفحة الترحيب في لارافيل وأنشأنا عملية تهجير لقاعدة بيانات MySQL. يمكن استخدام هذه الخطوات العملية في إنشاء تطبيق حقيقي يعتمد على المكونات السابقة.
</p>

<p>
	ترجمة وبتصرف للمقال <a href="https://www.digitalocean.com/community/tutorials/how-to-set-up-laravel-nginx-and-mysql-with-docker-compose" rel="external nofollow">How To Set Up Laravel, Nginx, and MySQL with Docker Compose</a> لصاحبه Faizan Bashir.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D9%84%D8%A7%D8%B1%D8%A7%D9%81%D9%8A%D9%84-laravel-%D8%B9%D9%84%D9%89-%D8%AF%D9%88%D9%83%D8%B1-%D9%83%D9%88%D9%85%D8%A8%D9%88%D8%B2-docker-compose-r650/" rel="">تثبيت وإعداد لارافيل Laravel على دوكر كومبوز Docker Compose</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-docker-compose-%D8%B9%D9%84%D9%89-%D8%AF%D8%A8%D9%8A%D8%A7%D9%86-r464/" rel="">كيفية تثبيت Docker Compose على دبيان</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/servers/web/nginx/%D8%AA%D9%86%D8%B5%D9%8A%D8%A8%D8%8C-%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D9%88%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-nginx-%D9%83%D8%AE%D8%A7%D8%AF%D9%88%D9%85-%D9%88%D9%8A%D8%A8-r1/" rel="">تنصيب، إعداد واستخدام nginx كخادوم ويب</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">651</guid><pubDate>Fri, 07 Oct 2022 08:41:28 +0000</pubDate></item><item><title>&#x62A;&#x62B;&#x628;&#x64A;&#x62A; &#x648;&#x625;&#x639;&#x62F;&#x627;&#x62F; &#x644;&#x627;&#x631;&#x627;&#x641;&#x64A;&#x644; Laravel &#x639;&#x644;&#x649; &#x62F;&#x648;&#x643;&#x631; &#x643;&#x648;&#x645;&#x628;&#x648;&#x632; Docker Compose</title><link>https://academy.hsoub.com/devops/cloud-computing/docker/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D9%84%D8%A7%D8%B1%D8%A7%D9%81%D9%8A%D9%84-laravel-%D8%B9%D9%84%D9%89-%D8%AF%D9%88%D9%83%D8%B1-%D9%83%D9%88%D9%85%D8%A8%D9%88%D8%B2-docker-compose-r650/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_10/633955f5f246f_------Docker-Compose--.jpg.f1d8c3f2d339973c7708fe33ac208e2a.jpg" /></p>

<p>
	يُعرّف الاحتواء ضمن حاوية على أنها عملية تكييف التطبيق ومكوناته لتصبح قادرةً على العمل ضمن بيئة بسيطة أساسية المقومات ندعوها <a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D9%85%D8%A7-%D9%87%D9%8A-%D8%B5%D9%88%D8%B1%D8%A9-%D8%A7%D9%84%D8%AD%D8%A7%D9%88%D9%8A%D8%A9-container-image%D8%9F-r587/" rel="">الحاوية</a> لتشكّل بيئةً معزولةً يمكن للمستخدم إزالتها بسهولة وسرعة عندما تنتهي حاجته منها أو عندما يرغب بتعديل بعضًا من أجزائها، كما يمكن الاستفادة منها لتطوير واختبار ونشر التطبيقات ضمن بيئات الإنتاج.
</p>

<p>
	نعمل في هذا المقال على استخدام الأداة دوكر كومبوز <a href="https://docs.docker.com/compose/" rel="external nofollow">Docker Compose</a> لاحتواء تطبيق <a href="https://academy.hsoub.com/programming/php/laravel/" rel="">لارافيل</a> لتطويره ضمن بيئة مستقلة خاصة به. نحصل على تطبيق لارافيل توضيحي موزّع على ثلاث حاويات خدمة معزولة عن بعضها يعضًا على النحو التالي:
</p>

<ul>
<li>
		خدمة تطبيق "app" تعمل على PHP7.4-FPM.
	</li>
	<li>
		خدمة قاعدة بيانات "db" تحتوي خادم MySQL 5.7.
	</li>
	<li>
		خدمة "nginx" تستخدم خدمة "app" من أجل تحليل شيفرة برمجية <a href="https://academy.hsoub.com/programming/php/%D8%A7%D9%84%D8%AF%D9%84%D9%8A%D9%84-%D8%A7%D9%84%D8%B3%D8%B1%D9%8A%D8%B9-%D8%A5%D9%84%D9%89-%D9%84%D8%BA%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-php-r609/" rel="">بلغة PHP</a> قبل تقديم تطبيق لارافيل للمستخدم النهائي.
	</li>
</ul>
<p>
	تُستخدم وحدات التخزين المشتركة Shared Volumes من أجل مزامنة ملفات التطبيق وذلك لتطوير تطبيق بسيط وتصحيح الأخطاء بسهولة؛ كما تُستخدم أوامر <code>docker-compose exec</code> لتشغيل تعليمات <a href="https://getcomposer.org/" rel="external nofollow">Composer</a> و <a href="https://laravel.com/docs/6.x/artisan" rel="external nofollow">artisan</a> في حاوية التطبيق.
</p>

<h2>
	المتطلبات الأساسية
</h2>

<ul>
<li>
		الوصول إلى حاسب يعمل بنظام التشغيل لينكس أوبنتو Ubuntu ذي رقم إصدار 20.04 أو إلى <a href="https://academy.hsoub.com/devops/servers/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%AE%D8%A7%D8%AF%D9%85-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-r574/" rel="">خادم</a> تطوير يعمل بنفس <a href="https://academy.hsoub.com/files/24-%D8%A3%D9%86%D8%B8%D9%85%D8%A9-%D8%A7%D9%84%D8%AA%D8%B4%D8%BA%D9%8A%D9%84-%D9%84%D9%84%D9%85%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D9%86/" rel="">نظام التشغيل</a> المذكور بواسطة مستخدم ذي صلاحيات إدارة sudo ولا يفضل استخدام حساب الجذر Root لأسباب تتعلق بأمان الخادم، كما يُفضل وجود جدار حماية firewall مثبت ونشط في حال استخدام خادم بعيد.
	</li>
	<li>
		تثبيت الأداة دوكر على الخادم أو الجهاز المحلي.
	</li>
	<li>
		تثبيت الأداة دوكر كومبوز Docker Compose على الخادم أو الجهاز المحلي.
	</li>
</ul>
<h2>
	الخطوة 1- الحصول على التطبيق التوضيحي
</h2>

<p>
	نبدأ بتنزيل تطبيق لارافيل التوضيحي من مخزن غيت هب <a href="https://github.com/do-community/travellist-laravel-demo" rel="external nofollow">Github</a> وننتقل إلى الفرع tutorial-01، الذي يتضمن تطبيقًا بسيطًا مبنيًا باستخدام لارافيل. نعتمد في هذا العمل على الإصدار tutorial-1.0.1 والذي نحملّه ضمن المجلد الأساسي للمستخدم بتنفيذ الأمر:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_6375_10" style="">
<span class="pln">cd </span><span class="pun">~</span><span class="pln">
curl </span><span class="pun">-</span><span class="pln">L https</span><span class="pun">:</span><span class="com">//github.com/do-community/travellist-laravel-demo/archive/tutorial-1.0.1.zip -o travellist.zip</span></pre>

<p>
	يبدأ تنزيل الملف المضغوط بصيغة "zip" لذلك يجب عند انتهاء التحميل فك الضغط باستخدام الأمر <code>unzip</code> بعد التأكد من تحديث الحزم الخاصة بنظام التشغيل ما لم تكن محدّثة مؤخرًا. ننفذ الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_6375_12" style="">
<span class="pln">sudo apt update
sudo apt install unzip</span></pre>

<p>
	ننفذ الأوامر التالي لفك ضغط الملف الذي حملناه ونعيد تسمية المجلد لاسم مناسب لسهولة الوصول إليه:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_6375_14" style="">
<span class="pln">unzip travellist</span><span class="pun">.</span><span class="pln">zip
mv travellist</span><span class="pun">-</span><span class="pln">laravel</span><span class="pun">-</span><span class="pln">demo</span><span class="pun">-</span><span class="pln">tutorial</span><span class="pun">-</span><span class="lit">1.0</span><span class="pun">.</span><span class="lit">1</span><span class="pln"> travellist</span><span class="pun">-</span><span class="pln">demo</span></pre>

<p>
	ننتقل إلى المجلّد "travellist-demo" بتنفيذ الأمر:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_6375_16" style="">
<span class="pln">cd travellist</span><span class="pun">-</span><span class="pln">demo</span></pre>

<h2>
	الخطوة 2- إعداد ملف الضبط الخاص بالتطبيق
</h2>

<p>
	نجد عادةً ملفات الضبط الخاصة بلارافيل ضمن المجلد "config" داخل المجلد الجذر للتطبيق. يحتوي الملف "env." معلومات الضبط المتعلقة بالبيئة، مثل بيانات الاعتماد وكلمات المرور وأية معلومات تختلف بين عمليات نشر التطبيقات، لذلك لا يُضمّن هذا الملف ضمن ملفات التحكم بالإصدارات لكي لا تنتشر هذه المعلومات لجميع المستخدمين الذين يستخدمون صورة التطبيق لاحقًا.
</p>

<p>
	<strong>تحذير</strong>: يحتوي ملف تهيئة البيئة على معلومات حساسة حول الخادم، بما في ذلك بيانات اعتماد قاعدة البيانات ومفاتيح الأمان، لهذا السبب لا يجب أبدًا مشاركة هذا الملف علنًا.
</p>

<p>
	توجد أولويةٌ للقيم الموجودة في الملف "env." على القيم الموجودة في ملفات التهيئة الأخرى الموجودة في المجلد "config". تتطلب كل عملية تثبيت في بيئة جديدة ملف بيئة مخصص لتعريف معلومات جديدة، مثل إعدادات الاتصال في قاعدة البيانات وخيارات التصحيح ورابط للتطبيق، إضافةً إلى بقية العناصر أو المعلومات التي تختلف تبعًا للبيئة التي يعمل بها التطبيق.
</p>

<p>
	ننشئ ملف "env." جديد لضبط خيارات التهيئة لبيئة التطوير التي نُعدها، ويمكننا نسخ الملف "example.env" المتاح افتراضيًا مع أي تطبيق لارافيل بتنفيذ الأمر:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_6375_18" style="">
<span class="pln">cp </span><span class="pun">.</span><span class="pln">env</span><span class="pun">.</span><span class="pln">example </span><span class="pun">.</span><span class="pln">env</span></pre>

<p>
	نحرر الملف باستخدام محرر النصوص نانو nano أو باختيار محرر النصوص المفضّل:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_6375_20" style="">
<span class="pln">nano </span><span class="pun">.</span><span class="pln">env</span></pre>

<p>
	يحتوي ملف "env." الحالي من تطبيق "travellist" التوضيحي على إعدادات لاستخدام التطبيق المُحتَوى المتصل <a href="https://academy.hsoub.com/devops/servers/databases/mysql/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A8%D8%B1%D9%86%D8%A7%D9%85%D8%AC-%D8%A5%D8%AF%D8%A7%D8%B1%D8%A9-%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-mysql-r28/" rel="">بقاعدة بيانات MySQL</a> محلية أي أنها متاحة على المضيف المحلي "127.0.0.1". نحتاج لتعديل قيمة المتغير <code>DB_HOST</code> لكي يشير إلى قاعدة البيانات التي ننشئها ضمن بيئة دوكر. نعتمد في عملنا على أنّ اسم خدمة قواعد البيانات هو <code>db</code>. نعدّل محتوى الملف لضبط قيم المتغيرات على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_6375_25" style="">
<span class="pln">APP_NAME</span><span class="pun">=</span><span class="typ">Travellist</span><span class="pln">
APP_ENV</span><span class="pun">=</span><span class="pln">dev
APP_KEY</span><span class="pun">=</span><span class="pln">
APP_DEBUG</span><span class="pun">=</span><span class="kwd">true</span><span class="pln">
APP_URL</span><span class="pun">=</span><span class="pln">http</span><span class="pun">:</span><span class="com">//localhost:8000</span><span class="pln">

LOG_CHANNEL</span><span class="pun">=</span><span class="pln">stack

DB_CONNECTION</span><span class="pun">=</span><span class="pln">mysql
DB_HOST</span><span class="pun">=</span><span class="pln">db
DB_PORT</span><span class="pun">=</span><span class="lit">3306</span><span class="pln">
DB_DATABASE</span><span class="pun">=</span><span class="pln">travellist
DB_USERNAME</span><span class="pun">=</span><span class="pln">travellist_user
DB_PASSWORD</span><span class="pun">=</span><span class="pln">password
</span><span class="pun">...</span></pre>

<p>
	نستطيع تغيير القيم الخاصة ببقية المتغيرات، مثل اسم قاعدة البيانات وكلمة المرور واسم المستخدم الذين سيُستخدمان عند إعداد الملف "docker-compose.yml" من أجل تهيئة الخدمات. نضغط على الاختصار "CTRL+X" ثم الحرف "Y" وأخيرًا زر الإدخال "Enter" بعد الانتهاء من تعديل الملف.
</p>

<h3>
	الخطوة 3 - إعداد ملف دوكر الخاص بالتطبيق
</h3>

<p>
	تستند خدمتا <a href="https://academy.hsoub.com/devops/servers/databases/mysql/%D8%AA%D8%B9%D9%84%D9%85-%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-mysql-r297/" rel="">MySQL</a> و <a href="https://academy.hsoub.com/devops/servers/web/nginx/%D8%AA%D9%86%D8%B5%D9%8A%D8%A8%D8%8C-%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D9%88%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-nginx-%D9%83%D8%AE%D8%A7%D8%AF%D9%88%D9%85-%D9%88%D9%8A%D8%A8-r1/" rel="">Nginx</a> على الصور الافتراضية التي يمكن الحصول عليها من <a href="https://hub.docker.com/" rel="external nofollow">Docker Hub</a>، لكن سنحتاج في مثالنا إلى إنشاء صورة مخصصة لحاوية التطبيق وسننشئ ملف Dockerfile جديد لذلك.
</p>

<p>
	تعتمد صورة التطبيق travillist على <a href="https://hub.docker.com/_/php" rel="external nofollow">صورة PHP الرسمية</a> ذات الاسم php:7.4-fpm والمتاحة ضمن مخزن دوكر هب Docker Hub. نحتاج تثبيت بعض حزم PHP الإضافية باستخدام أداة إدارة الاعتمادية <a href="https://getcomposer.org/" rel="external nofollow">Composer</a>.
</p>

<p>
	ننشئ مستخدم نظام جديد لتنفيذ الأوامر المختلفة مثل أوامر"Composer" و "artisan" أثناء تطوير التطبيق. يضمن إعداد "uid" امتلاك المستخدم الموجود داخل <a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D8%AD%D8%A7%D9%88%D9%8A%D8%A7%D8%AA-r641/" rel="">الحاوية</a> نفس معرف المستخدم الخاص بمستخدم النظام على الجهاز المضيف أثناء تشغيل دوكر. تتزامن الملفات التي تُنشأ من الأوامر في جهاز المضيف باستخدام الأذونات الصحيحة، ونستطيع استخدام أي محرر نصوص لتطوير الشيفرة البرمجية للتطبيق داخل الحاويات. ننشئ ملف Dockerfile جديد بتنفيذ الأمر:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_6375_30" style="">
<span class="pln">nano </span><span class="typ">Dockerfile</span></pre>

<p>
	ننسخ المحتوى التالي إلى ملف Dockerfile:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_6375_32" style="">
<span class="pln">FROM php</span><span class="pun">:</span><span class="lit">7.4</span><span class="pun">-</span><span class="pln">fpm

</span><span class="com"># Arguments defined in docker-compose.yml</span><span class="pln">
ARG user
ARG uid

</span><span class="com"># Install system dependencies</span><span class="pln">
RUN apt</span><span class="pun">-</span><span class="kwd">get</span><span class="pln"> update </span><span class="pun">&amp;&amp;</span><span class="pln"> apt</span><span class="pun">-</span><span class="kwd">get</span><span class="pln"> install </span><span class="pun">-</span><span class="pln">y \
    git \
    curl \
    libpng</span><span class="pun">-</span><span class="pln">dev \
    libonig</span><span class="pun">-</span><span class="pln">dev \
    libxml2</span><span class="pun">-</span><span class="pln">dev \
    zip \
    unzip

</span><span class="com"># Clear cache</span><span class="pln">
RUN apt</span><span class="pun">-</span><span class="kwd">get</span><span class="pln"> clean </span><span class="pun">&amp;&amp;</span><span class="pln"> rm </span><span class="pun">-</span><span class="pln">rf </span><span class="pun">/</span><span class="kwd">var</span><span class="pun">/</span><span class="pln">lib</span><span class="pun">/</span><span class="pln">apt</span><span class="pun">/</span><span class="pln">lists</span><span class="com">/*

# Install PHP extensions
RUN docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd

# Get latest Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer

# Create system user to run Composer and Artisan Commands
RUN useradd -G www-data,root -u $uid -d /home/$user $user
RUN mkdir -p /home/$user/.composer &amp;&amp; \
    chown -R $user:$user /home/$user

# Set working directory
WORKDIR /var/www

USER $user</span></pre>

<p>
	نحفظ الملف بعد الانتهاء من التعديل عليه. يبدأ الملف بتحديد الصورة الأساس التي نستخدمها وهي في حالة الملف السابق <code>php:7.4-fpm</code>. نثبت أداة Composer بنسخ الشكل التنفيذي الخاص بالأمر "composer" والمتاح ضمن أحدث <a href="https://hub.docker.com/_/composer" rel="external nofollow">صورة رسمية</a> إلى صورة التطبيق الخاص بنا.
</p>

<p>
	ننشئ مستخدم نظام جديد ونبدأ بإعداد المتغيرين<code>user</code> و <code>uid</code> الذين عرّفناهما في بداية الملف Dockerfile، إذ تُمرَّر هذه المتغيرات إلى الحاوية باستخدام الأداة دوكر كومبوز في وقت البناء.
</p>

<p>
	نحدد مسار العمل الافتراضي "var/www/" ونسجّل الدخول بواسطة المستخدم الذي أنشأناه مؤخرًا للتأكد من الاتصال بقاعدة البيانات بصفة مستخدم عادي وأننا في المسار الصحيح عند استخدام الأوامر المختلفة، مثل أوامر"composer" و "artisan" في حاوية التطبيق.
</p>

<h2>
	الخطوة 4 - إعداد ضبط Nginx وملفات إفراغ قاعدة البيانات
</h2>

<p>
	يجب مشاركة ملفات الضبط والتهيئة مع حاويات الخدمة عند إنشاء بيئة تطوير باستخدام أداة دوكر كومبوزلإعداد أو التمهيد لهذه الخدمات، إذ يسهّل ذلك إجراء التعديلات على ملفات الضبط وضبط البيئة أثناء تطوير التطبيق. ننشئ مجلد ونضع به الملفات التي تستخدم في تهيئة وبناء حاويات الخدمة. ننشئ مجلد "docker-compose/nginx" ونضع به ملف "travellist.conf" الذي يهيئ آلية تقديم التطبيق وذلك بتنفيذ الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_6375_34" style="">
<span class="pln">mkdir </span><span class="pun">-</span><span class="pln">p docker</span><span class="pun">-</span><span class="pln">compose</span><span class="pun">/</span><span class="pln">nginx</span></pre>

<p>
	نعدّل محتوى الملف "travellist.conf" باستخدام محرر النصوص المفضل عبر تنفيذ الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_6375_36" style="">
<span class="pln">nano docker</span><span class="pun">-</span><span class="pln">compose</span><span class="pun">/</span><span class="pln">nginx</span><span class="pun">/</span><span class="pln">travellist</span><span class="pun">.</span><span class="pln">conf</span></pre>

<p>
	نضيف الشيفرة البرمجية التالية داخله:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_6375_38" style="">
<span class="pln">server </span><span class="pun">{</span><span class="pln">
    listen </span><span class="lit">80</span><span class="pun">;</span><span class="pln">
    index index</span><span class="pun">.</span><span class="pln">php index</span><span class="pun">.</span><span class="pln">html</span><span class="pun">;</span><span class="pln">
    error_log  </span><span class="pun">/</span><span class="kwd">var</span><span class="pun">/</span><span class="pln">log</span><span class="pun">/</span><span class="pln">nginx</span><span class="pun">/</span><span class="pln">error</span><span class="pun">.</span><span class="pln">log</span><span class="pun">;</span><span class="pln">
    access_log </span><span class="pun">/</span><span class="kwd">var</span><span class="pun">/</span><span class="pln">log</span><span class="pun">/</span><span class="pln">nginx</span><span class="pun">/</span><span class="pln">access</span><span class="pun">.</span><span class="pln">log</span><span class="pun">;</span><span class="pln">
    root </span><span class="pun">/</span><span class="kwd">var</span><span class="pun">/</span><span class="pln">www</span><span class="pun">/</span><span class="kwd">public</span><span class="pun">;</span><span class="pln">
    location </span><span class="pun">~</span><span class="pln"> \.php$ </span><span class="pun">{</span><span class="pln">
        try_files $uri </span><span class="pun">=</span><span class="lit">404</span><span class="pun">;</span><span class="pln">
        fastcgi_split_path_info </span><span class="pun">^(.+</span><span class="pln">\.php</span><span class="pun">)(/.+)</span><span class="pln">$</span><span class="pun">;</span><span class="pln">
        fastcgi_pass app</span><span class="pun">:</span><span class="lit">9000</span><span class="pun">;</span><span class="pln">
        fastcgi_index index</span><span class="pun">.</span><span class="pln">php</span><span class="pun">;</span><span class="pln">
        include fastcgi_params</span><span class="pun">;</span><span class="pln">
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name</span><span class="pun">;</span><span class="pln">
        fastcgi_param PATH_INFO $fastcgi_path_info</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    location </span><span class="pun">/</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        try_files $uri $uri</span><span class="pun">/</span><span class="pln"> </span><span class="pun">/</span><span class="pln">index</span><span class="pun">.</span><span class="pln">php</span><span class="pun">?</span><span class="pln">$query_string</span><span class="pun">;</span><span class="pln">
        gzip_static on</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	يضبط الملف السابق خادم Nginx ويجبره على الاستماع للمنفذ <code>80</code> ويستخدم الصفحة <code>index.php</code> لتكون صفحة الدليل الأساسية للتطبيق. نضبط المسار الجذر Root للمستند ليصبح "‎/var/www/public" ونضبط خادم Nginx ونجبره على استخدام المنفذ "9000" لمعالجة الملفات ذات اللاحقة "php.*". احفظ الملف وأغلقه عند الانتهاء من التعديل.
</p>

<p>
	سنشارك من أجل إكمال إعداد قاعدة البيانات MYSQL ملفًا يتضمن محتوى قاعدة البيانات database dump، الذي سيُستورد محتواه إلى قاعدة البيانات عندما نهيئ الحاوية لأول مرة، وتتوفر هذه الميزة في <a href="https://hub.docker.com/_/mysql" rel="external nofollow">MySQL 5.7</a> التي نستخدمها في حاوية التطبيق. ننشئ مجلدًا جديدًا لتهيئة ملفات قاعدة البيانات MySQL داخل المجلد "docker-compose" بتنفيذ الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_6375_42" style="">
<span class="pln">mkdir docker</span><span class="pun">-</span><span class="pln">compose</span><span class="pun">/</span><span class="pln">mysql</span></pre>

<p>
	ننشئ ملف "sql." جديد بتنفيذ الأمر:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_6375_44" style="">
<span class="pln">nano docker</span><span class="pun">-</span><span class="pln">compose</span><span class="pun">/</span><span class="pln">mysql</span><span class="pun">/</span><span class="pln">init_db</span><span class="pun">.</span><span class="pln">sql</span></pre>

<p>
	يعتمد ملف قاعدة البيانات MySQL dump التالي على قاعدة البيانات المُنشأة في دليل <a href="https://www.digitalocean.com/community/tutorials/how-to-install-and-configure-laravel-with-nginx-on-ubuntu-20-04" rel="external nofollow">لارافيل بالاعتماد على LEMP</a>، إذ يُنشئ جدولًا اسمه <code>places</code> في قاعدة البيانات ثم سيملأ محتوياته بمجموعة من العينات.
</p>

<p>
	اكتب الشيفرة البرمجية التالية في الملف "db_init.sql" ليصبح بالشكل التالي:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_6375_46" style="">
<span class="pln">DROP TABLE IF EXISTS </span><span class="str">`places`</span><span class="pun">;</span><span class="pln">

CREATE TABLE </span><span class="str">`places`</span><span class="pln"> </span><span class="pun">(</span><span class="pln">
  </span><span class="str">`id`</span><span class="pln"> bigint</span><span class="pun">(</span><span class="lit">20</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">unsigned</span><span class="pln"> NOT NULL AUTO_INCREMENT</span><span class="pun">,</span><span class="pln">
  </span><span class="str">`name`</span><span class="pln"> varchar</span><span class="pun">(</span><span class="lit">255</span><span class="pun">)</span><span class="pln"> COLLATE utf8mb4_unicode_ci NOT NULL</span><span class="pun">,</span><span class="pln">
  </span><span class="str">`visited`</span><span class="pln"> tinyint</span><span class="pun">(</span><span class="lit">1</span><span class="pun">)</span><span class="pln"> NOT NULL DEFAULT </span><span class="str">'0'</span><span class="pun">,</span><span class="pln">
  PRIMARY KEY </span><span class="pun">(</span><span class="str">`id`</span><span class="pun">)</span><span class="pln">
</span><span class="pun">)</span><span class="pln"> ENGINE</span><span class="pun">=</span><span class="typ">InnoDB</span><span class="pln"> AUTO_INCREMENT</span><span class="pun">=</span><span class="lit">12</span><span class="pln"> DEFAULT CHARSET</span><span class="pun">=</span><span class="pln">utf8mb4 COLLATE</span><span class="pun">=</span><span class="pln">utf8mb4_unicode_ci</span><span class="pun">;</span><span class="pln">

INSERT INTO </span><span class="str">`places`</span><span class="pln"> </span><span class="pun">(</span><span class="pln">name</span><span class="pun">,</span><span class="pln"> visited</span><span class="pun">)</span><span class="pln"> VALUES </span><span class="pun">(</span><span class="str">'Berlin'</span><span class="pun">,</span><span class="lit">0</span><span class="pun">),(</span><span class="str">'Budapest'</span><span class="pun">,</span><span class="lit">0</span><span class="pun">),(</span><span class="str">'Cincinnati'</span><span class="pun">,</span><span class="lit">1</span><span class="pun">),(</span><span class="str">'Denver'</span><span class="pun">,</span><span class="lit">0</span><span class="pun">),(</span><span class="str">'Helsinki'</span><span class="pun">,</span><span class="lit">0</span><span class="pun">),(</span><span class="str">'Lisbon'</span><span class="pun">,</span><span class="lit">0</span><span class="pun">),(</span><span class="str">'Moscow'</span><span class="pun">,</span><span class="lit">1</span><span class="pun">),(</span><span class="str">'Nairobi'</span><span class="pun">,</span><span class="lit">0</span><span class="pun">),(</span><span class="str">'Oslo'</span><span class="pun">,</span><span class="lit">1</span><span class="pun">),(</span><span class="str">'Rio'</span><span class="pun">,</span><span class="lit">0</span><span class="pun">),(</span><span class="str">'Tokyo'</span><span class="pun">,</span><span class="lit">0</span><span class="pun">);</span></pre>

<p>
	يتضمن الجدول <code>places</code> ثلاثة حقول <code>id</code> و <code>name</code> و <code>visited</code> وهي على الترتيب معرّف خاص بكل مكان واسم المكان ومتغير يشير إلى زيارة المكان مسبقًا أم لا، ويمكن تعديل أسماء الحقول وإضافة حقول جديدة عند الحاجة.
</p>

<h3>
	الخطوة 5 - إنشاء بيئة متعددة الحاويات بواسطة دوكر كومبوز
</h3>

<p>
	تُنشئ الأداة دوكر كومبوز بيئات متعددة الحاويات للتطبيقات التي تعمل على دوكر، كما تستخدم تعريفات الخدمة لبناء بيئات قابلة للتخصيص بالكامل مع حاويات متعددة يمكنها مشاركة الشبكات وأحجام البيانات، مما يتيح التكامل السلس بين مكونات التطبيق المختلفة.
</p>

<p>
	ننشئ ملفًا جديدًا يسمى "docker-compose.yml"، والذي يمكن أن يكون موجودًا مسبقًا في مجلد الجذر للتطبيق، إذ يحدد هذا الملف بيئة الحاويات بما في ذلك الصور الأساسية لبناء التطبيق وكيفية تفاعل الخدمات ضمنه.
</p>

<p>
	نحدد ثلاث خدمات مختلفة ضمن ملف "docker-compose.yml"، هي: "app" و "db" و "nginx"؛ إذ تُنشئ خدمة "app" صورةً تسمى "travellist" بناءً على ملف Dockerfile الذي أنشأناه سابقًا، وتُشغّل الحاوية التي تحددها هذه الخدمة خادم "php-fpm" لتحليل شيفرات PHP وتُرسل النتائج مرةً أخرى إلى خدمة "nginx، التي تعمل على حاوية منفصلة. تحدد خدمة "mysql" حاويةً تُشغل خادم MySQL 5.7. تتصل هذه الخدمات فيما بينها باستخدام <a href="https://docs.docker.com/network/bridge/" rel="external nofollow">شبكة جسر</a> أسميناها "travellist".
</p>

<p>
	تُزامن ملفات التطبيق على كل من الخدمتين "app" و "nginx" عبر <a href="https://docs.docker.com/storage/bind-mounts/" rel="external nofollow">حوامل الربط</a>، التي تُعد مفيدةً في بيئات التطوير لأنها تتيح مزامنةً ثنائية الاتجاه بين الجهاز المضيف والحاويات. نُنشئ ملفًا جديدًا باسم "docker-compose.yml" في مجلد الجذر للتطبيق بتنفيذ الأمر:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_6375_48" style="">
<span class="pln">nano docker</span><span class="pun">-</span><span class="pln">compose</span><span class="pun">.</span><span class="pln">yml</span></pre>

<p>
	يبدأ ملف "docker-compose.yml" التقليدي برقم الإصدار يليها تعريف الخدمات "services" الخاصة بالتطبيق، كما تُعرَّف الشبكات التي يشاركها التطبيق في نهاية الملف. إذًا، يكون محتوى هذا الملف كما يلي:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_6375_50" style="">
<span class="pln">version</span><span class="pun">:</span><span class="pln"> </span><span class="str">"3.7"</span><span class="pln">
services</span><span class="pun">:</span><span class="pln">


networks</span><span class="pun">:</span><span class="pln">
  travellist</span><span class="pun">:</span><span class="pln">
    driver</span><span class="pun">:</span><span class="pln"> bridge</span></pre>

<p>
	سنعدّل الآن عقدة "services" لتتضمن خدمات "app" و "db" و "nginx".
</p>

<h4>
	خدمة التطبيق app
</h4>

<p>
	تُعِد خدمة "app" حاويةً باسم "travellist-app" وتبني صورة دوكر جديدة بناءً على Dockerfile الموجود في نفس مسار الملف "docker-compose.yml" وتُحفظ الصورة محليًا باسم "travellist". نحتاج تواجد ملفات التطبيق ضمن حاوية التطبيق "app" حتى ولو كانت هذه الملفات موجودةً في جذر المستند الذي جرى تقديمه على أنه تطبيق موجود في حاوية "nginx"، ونحتاج ذلك من أجل تنفيذ أوامر "artisan" الخاصة بإطار عمل لارافيل على هذه الملفات. نضيف البيانات التالية إلى الملف "docker-compose.yml":
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_6375_52" style="">
<span class="pln">app</span><span class="pun">:</span><span class="pln">
    build</span><span class="pun">:</span><span class="pln">
      args</span><span class="pun">:</span><span class="pln">
        user</span><span class="pun">:</span><span class="pln"> sammy
        uid</span><span class="pun">:</span><span class="pln"> </span><span class="lit">1000</span><span class="pln">
      context</span><span class="pun">:</span><span class="pln"> </span><span class="pun">./</span><span class="pln">
      dockerfile</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Dockerfile</span><span class="pln">
    image</span><span class="pun">:</span><span class="pln"> travellist
    container_name</span><span class="pun">:</span><span class="pln"> travellist</span><span class="pun">-</span><span class="pln">app
    restart</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">unless</span><span class="pun">-</span><span class="pln">stopped
    working_dir</span><span class="pun">:</span><span class="pln"> </span><span class="str">/var/</span><span class="pln">www</span><span class="pun">/</span><span class="pln">
    volumes</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> </span><span class="pun">./:</span><span class="str">/var/</span><span class="pln">www
    networks</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> travellist</span></pre>

<p>
	تحقق هذه الإعدادات ما يلي:
</p>

<ul>
<li>
		<code>build</code>: يعلِم هذا الضبط أداة دوكر كومبوز ببناء صورة محلية لخدمة التطبيق، باستخدام المسار المحدد context وملف Dockerfile للحصول على التعليمات. تُحقن المتغيرات <code>user</code> و <code>uid</code> في ملف Dockerfile لتخصيص أوامر إنشاء المستخدم في وقت الإنشاء.
	</li>
	<li>
		<code>image</code>: تُعدّ الاسم الذي يستخدم الصورة قيد الإنشاء.
	</li>
	<li>
		<code>container_name</code>: تُعِدّ اسم الحاوية لهذه الخدمة.
	</li>
	<li>
		<code>restart</code>: يعيد التشغيل دائمًا، ما لم يحدث إيقافٌ الخدمة.
	</li>
	<li>
		<code>working_dir</code>: يعيّن المجلّد الافتراضي لهذه الخدمة، مثل "‎/var/www".
	</li>
	<li>
		<code>volumes</code>: يُنشئ وحدة تخزين مشتركة تُزامِن المحتويات من المسار الحالي إلى "‎/var/www" داخل الحاوية، ويبقى موجودًا في حاوية "nginx".
	</li>
	<li>
		<code>networks</code>: تُعِدّ هذه الخدمة لاستخدام شبكة باسم "travellist".
	</li>
</ul>
<h4>
	خدمة قاعدة البيانات db
</h4>

<p>
	تستخدم خدمة "db" صورة <a href="https://hub.docker.com/_/mysql" rel="external nofollow">MySQL 5.7</a> من مخزن دوكر هب. تحمّل الأداة دوكر كومبوز الملفات المتغيرة "env." الموجودة في نفس المسار الذي يحتوي الملف "docker-compose.yml"، ونحصل على إعدادات قاعدة البيانات ملف "env." الذي قد ولّدناه عند إنشاء تطبيق لارافيل. نضع البيانات التالية بعد البيانات التي وضعناها للخدمة "app":
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_6375_54" style="">
<span class="pln">db</span><span class="pun">:</span><span class="pln">
    image</span><span class="pun">:</span><span class="pln"> mysql</span><span class="pun">:</span><span class="lit">5.7</span><span class="pln">
    container_name</span><span class="pun">:</span><span class="pln"> travellist</span><span class="pun">-</span><span class="pln">db
    restart</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">unless</span><span class="pun">-</span><span class="pln">stopped
    environment</span><span class="pun">:</span><span class="pln">
      MYSQL_DATABASE</span><span class="pun">:</span><span class="pln"> $</span><span class="pun">{</span><span class="pln">DB_DATABASE</span><span class="pun">}</span><span class="pln">
      MYSQL_ROOT_PASSWORD</span><span class="pun">:</span><span class="pln"> $</span><span class="pun">{</span><span class="pln">DB_PASSWORD</span><span class="pun">}</span><span class="pln">
      MYSQL_PASSWORD</span><span class="pun">:</span><span class="pln"> $</span><span class="pun">{</span><span class="pln">DB_PASSWORD</span><span class="pun">}</span><span class="pln">
      MYSQL_USER</span><span class="pun">:</span><span class="pln"> $</span><span class="pun">{</span><span class="pln">DB_USERNAME</span><span class="pun">}</span><span class="pln">
      SERVICE_TAGS</span><span class="pun">:</span><span class="pln"> dev
      SERVICE_NAME</span><span class="pun">:</span><span class="pln"> mysql
    volumes</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> </span><span class="pun">./</span><span class="pln">docker</span><span class="pun">-</span><span class="pln">compose</span><span class="pun">/</span><span class="pln">mysql</span><span class="pun">:/</span><span class="pln">docker</span><span class="pun">-</span><span class="pln">entrypoint</span><span class="pun">-</span><span class="pln">initdb</span><span class="pun">.</span><span class="pln">d
    networks</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> travellist</span></pre>

<p>
	تحقق هذه الإعدادات ما يلي:
</p>

<ul>
<li>
		<code>image</code>: تحدد صورة دوكر التي ستستخدم في هذه الحاوية. نستخدم صورة MySQL 5.7 من دوكر هب Docker Hub.
	</li>
	<li>
		<code>container_name</code>: اسم الحاوية <code>travellist-db</code>.
	</li>
	<li>
		<code>restart</code>: يعيد التشغيل دائمًا، ما لم يحدث إيقاف للخدمة.
	</li>
	<li>
		<code>environment</code>: تُحدد متغيرات البيئة في الحاوية الجديدة. نستخدم القيم التي حصلنا عليها من ملف "env." لإعداد خدمة قاعدة البيانات MySQL والتي تنشئ تلقائيًا قاعدة بيانات ومستخدمًا جديدًا بناءً على متغيرات البيئة المتوفرة.
	</li>
	<li>
		<code>volumes</code>: ينشئ وحدة تخزين لتهيئة قاعدة بيانات التطبيق. تستورد صورة MySQL ملفات"sql." تلقائيًا والتي توجد في المسار <code>‎/docker-entrypoint-initdb.d</code> داخل الحاوية.
	</li>
	<li>
		<code>network</code>: تُعِد هذه الخدمة لاستخدام شبكة باسم <code>travellist</code>.
	</li>
</ul>
<h4>
	الخدمة nginx
</h4>

<p>
	تستخدم خدمة "nginx" صورة <a href="https://hub.docker.com/_/nginx" rel="external nofollow">Nginx</a> مبنية مسبقًا على توزيعة لينكس اسمها <a href="https://wiki.alpinelinux.org/wiki/Main_Page" rel="external nofollow">Alpine</a>. تنشئ حاوية باسم "travellist-nginx" وتُعيد التوجيه من المنفذ "8000" الى المنفذ "80" داخل الحاوية. نضع البيانات التالية بعد بيانات خدمة "db":
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_6375_56" style="">
<span class="pln"> nginx</span><span class="pun">:</span><span class="pln">
    image</span><span class="pun">:</span><span class="pln"> nginx</span><span class="pun">:</span><span class="lit">1.17</span><span class="pun">-</span><span class="pln">alpine
    container_name</span><span class="pun">:</span><span class="pln"> travellist</span><span class="pun">-</span><span class="pln">nginx
    restart</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">unless</span><span class="pun">-</span><span class="pln">stopped
    ports</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> </span><span class="lit">8000</span><span class="pun">:</span><span class="lit">80</span><span class="pln">
    volumes</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> </span><span class="pun">./:</span><span class="str">/var/</span><span class="pln">www
      </span><span class="pun">-</span><span class="pln"> </span><span class="pun">./</span><span class="pln">docker</span><span class="pun">-</span><span class="pln">compose</span><span class="pun">/</span><span class="pln">nginx</span><span class="pun">:</span><span class="str">/etc/</span><span class="pln">nginx</span><span class="pun">/</span><span class="pln">conf</span><span class="pun">.</span><span class="pln">d
    networks</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> travellist</span></pre>

<p>
	تحقق هذه الإعدادات ما يلي:
</p>

<ul>
<li>
		<code>image</code>: اسم صورة دوكر التي تستخدم في هذه الحاوية وهي في هذه الحالة الصورة Alpine Nginx 1.17 .
	</li>
	<li>
		<code>container_name</code>: اسم الحاوية <code>travellist-nginx</code>.
	</li>
	<li>
		<code>restart</code>: يعيد التشغيل دائمًا، ما لم يحدث إيقافٌ للخدمة.
	</li>
	<li>
		<code>ports</code>: تعيد توجيه المنفذ الذي يسمح بالوصول الخارجي عبر المنفذ <code>8000</code> إلى خادم الويب الذي يعمل على المنفذ <code>80</code> داخل الحاوية.
	</li>
	<li>
		<code>volumes</code>: تنشئ وحدتي تخزين مشتركتين، بحيث تزامن الوحدة الأولى المحتويات من نقطة العمل إلى المسار <code>‎/var/www</code> داخل الحاوية ويفيد ذلك بنقل التعديلات المحلية إلى التطبيق الذي يقدّمه Nginx تلقائيًا. تنسخ الوحدة الثانية ملفات الإعداد الخاصة بخدمة nginx الموجودة في الملف "docker-compose/nginx/travellist.conf" الى ملفات الإعداد الخاصة بخدمة nginx ضمن الحاوية.
	</li>
	<li>
		<code>network</code>: تُعِد هذه الخدمة لاستخدام شبكة باسم <code>travellist</code>.
	</li>
</ul>
<p>
	وعليه يصبح ملف "docker-compose.yml" على الشكل التالي:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_6375_60" style="">
<span class="pln">version</span><span class="pun">:</span><span class="pln"> </span><span class="str">"3.7"</span><span class="pln">
services</span><span class="pun">:</span><span class="pln">
  app</span><span class="pun">:</span><span class="pln">
    build</span><span class="pun">:</span><span class="pln">
      args</span><span class="pun">:</span><span class="pln">
        user</span><span class="pun">:</span><span class="pln"> sammy
        uid</span><span class="pun">:</span><span class="pln"> </span><span class="lit">1000</span><span class="pln">
      context</span><span class="pun">:</span><span class="pln"> </span><span class="pun">./</span><span class="pln">
      dockerfile</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Dockerfile</span><span class="pln">
    image</span><span class="pun">:</span><span class="pln"> travellist
    container_name</span><span class="pun">:</span><span class="pln"> travellist</span><span class="pun">-</span><span class="pln">app
    restart</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">unless</span><span class="pun">-</span><span class="pln">stopped
    working_dir</span><span class="pun">:</span><span class="pln"> </span><span class="str">/var/</span><span class="pln">www</span><span class="pun">/</span><span class="pln">
    volumes</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> </span><span class="pun">./:</span><span class="str">/var/</span><span class="pln">www
    networks</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> travellist

  db</span><span class="pun">:</span><span class="pln">
    image</span><span class="pun">:</span><span class="pln"> mysql</span><span class="pun">:</span><span class="lit">5.7</span><span class="pln">
    container_name</span><span class="pun">:</span><span class="pln"> travellist</span><span class="pun">-</span><span class="pln">db
    restart</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">unless</span><span class="pun">-</span><span class="pln">stopped
    environment</span><span class="pun">:</span><span class="pln">
      MYSQL_DATABASE</span><span class="pun">:</span><span class="pln"> $</span><span class="pun">{</span><span class="pln">DB_DATABASE</span><span class="pun">}</span><span class="pln">
      MYSQL_ROOT_PASSWORD</span><span class="pun">:</span><span class="pln"> $</span><span class="pun">{</span><span class="pln">DB_PASSWORD</span><span class="pun">}</span><span class="pln">
      MYSQL_PASSWORD</span><span class="pun">:</span><span class="pln"> $</span><span class="pun">{</span><span class="pln">DB_PASSWORD</span><span class="pun">}</span><span class="pln">
      MYSQL_USER</span><span class="pun">:</span><span class="pln"> $</span><span class="pun">{</span><span class="pln">DB_USERNAME</span><span class="pun">}</span><span class="pln">
      SERVICE_TAGS</span><span class="pun">:</span><span class="pln"> dev
      SERVICE_NAME</span><span class="pun">:</span><span class="pln"> mysql
    volumes</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> </span><span class="pun">./</span><span class="pln">docker</span><span class="pun">-</span><span class="pln">compose</span><span class="pun">/</span><span class="pln">mysql</span><span class="pun">:/</span><span class="pln">docker</span><span class="pun">-</span><span class="pln">entrypoint</span><span class="pun">-</span><span class="pln">initdb</span><span class="pun">.</span><span class="pln">d
    networks</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> travellist

  nginx</span><span class="pun">:</span><span class="pln">
    image</span><span class="pun">:</span><span class="pln"> nginx</span><span class="pun">:</span><span class="pln">alpine
    container_name</span><span class="pun">:</span><span class="pln"> travellist</span><span class="pun">-</span><span class="pln">nginx
    restart</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">unless</span><span class="pun">-</span><span class="pln">stopped
    ports</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> </span><span class="lit">8000</span><span class="pun">:</span><span class="lit">80</span><span class="pln">
    volumes</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> </span><span class="pun">./:</span><span class="str">/var/</span><span class="pln">www
      </span><span class="pun">-</span><span class="pln"> </span><span class="pun">./</span><span class="pln">docker</span><span class="pun">-</span><span class="pln">compose</span><span class="pun">/</span><span class="pln">nginx</span><span class="pun">:</span><span class="str">/etc/</span><span class="pln">nginx</span><span class="pun">/</span><span class="pln">conf</span><span class="pun">.</span><span class="pln">d</span><span class="pun">/</span><span class="pln">
    networks</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> travellist

networks</span><span class="pun">:</span><span class="pln">
  travellist</span><span class="pun">:</span><span class="pln">
    driver</span><span class="pun">:</span><span class="pln"> bridge</span></pre>

<p>
	نتأكد من حفظ الملف بعد الانتهاء من إجراء التعديلات السابقة.
</p>

<h3>
	الخطوة 6 - تشغيل التطبيق عبر أداة دوكر كومبوز
</h3>

<p>
	ننشئ صورة تطبيق جديد ونشغّل الخدمات التي حددناها ضمن الإعدادات باستخدام الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_6375_62" style="">
<span class="pln">docker</span><span class="pun">-</span><span class="pln">compose build app</span></pre>

<p>
	يستغرق الأمر بضعة دقائق، ثم يظهر الخرج التالي:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_6375_64" style="">
<span class="typ">Building</span><span class="pln"> app
</span><span class="typ">Step</span><span class="pln"> </span><span class="lit">1</span><span class="pun">/</span><span class="lit">11</span><span class="pln"> </span><span class="pun">:</span><span class="pln"> FROM php</span><span class="pun">:</span><span class="lit">7.4</span><span class="pun">-</span><span class="pln">fpm
 </span><span class="pun">---&gt;</span><span class="pln"> fa37bd6db22a
</span><span class="typ">Step</span><span class="pln"> </span><span class="lit">2</span><span class="pun">/</span><span class="lit">11</span><span class="pln"> </span><span class="pun">:</span><span class="pln"> ARG user
 </span><span class="pun">---&gt;</span><span class="pln"> </span><span class="typ">Running</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> f71eb33b7459
</span><span class="typ">Removing</span><span class="pln"> intermediate container f71eb33b7459
 </span><span class="pun">---&gt;</span><span class="pln"> </span><span class="lit">533c30216f34</span><span class="pln">
</span><span class="typ">Step</span><span class="pln"> </span><span class="lit">3</span><span class="pun">/</span><span class="lit">11</span><span class="pln"> </span><span class="pun">:</span><span class="pln"> ARG uid
 </span><span class="pun">---&gt;</span><span class="pln"> </span><span class="typ">Running</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> </span><span class="lit">60d2d2a84cda</span><span class="pln">
</span><span class="typ">Removing</span><span class="pln"> intermediate container </span><span class="lit">60d2d2a84cda</span><span class="pln">
 </span><span class="pun">---&gt;</span><span class="pln"> </span><span class="lit">497fbf904605</span><span class="pln">
</span><span class="typ">Step</span><span class="pln"> </span><span class="lit">4</span><span class="pun">/</span><span class="lit">11</span><span class="pln"> </span><span class="pun">:</span><span class="pln"> RUN apt</span><span class="pun">-</span><span class="kwd">get</span><span class="pln"> update </span><span class="pun">&amp;&amp;</span><span class="pln"> apt</span><span class="pun">-</span><span class="kwd">get</span><span class="pln"> install </span><span class="pun">-</span><span class="pln">y     git     curl     libpng</span><span class="pun">-</span><span class="pln">dev     libonig</span><span class="pun">-</span><span class="pln">dev     </span><span class="pun">...</span><span class="pln">
</span><span class="typ">Step</span><span class="pln"> </span><span class="lit">7</span><span class="pun">/</span><span class="lit">11</span><span class="pln"> </span><span class="pun">:</span><span class="pln"> COPY </span><span class="pun">--</span><span class="kwd">from</span><span class="pun">=</span><span class="pln">composer</span><span class="pun">:</span><span class="pln">latest </span><span class="pun">/</span><span class="pln">usr</span><span class="pun">/</span><span class="pln">bin</span><span class="pun">/</span><span class="pln">composer </span><span class="pun">/</span><span class="pln">usr</span><span class="pun">/</span><span class="pln">bin</span><span class="pun">/</span><span class="pln">composer
 </span><span class="pun">---&gt;</span><span class="pln"> e499f74896e3
</span><span class="typ">Step</span><span class="pln"> </span><span class="lit">8</span><span class="pun">/</span><span class="lit">11</span><span class="pln"> </span><span class="pun">:</span><span class="pln"> RUN useradd </span><span class="pun">-</span><span class="pln">G www</span><span class="pun">-</span><span class="pln">data</span><span class="pun">,</span><span class="pln">root </span><span class="pun">-</span><span class="pln">u $uid </span><span class="pun">-</span><span class="pln">d </span><span class="pun">/</span><span class="pln">home</span><span class="pun">/</span><span class="pln">$user $user
 </span><span class="pun">---&gt;</span><span class="pln"> </span><span class="typ">Running</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> </span><span class="lit">232ef9c7dbd1</span><span class="pln">
</span><span class="typ">Removing</span><span class="pln"> intermediate container </span><span class="lit">232ef9c7dbd1</span><span class="pln">
 </span><span class="pun">---&gt;</span><span class="pln"> </span><span class="lit">870fa3220ffa</span><span class="pln">
</span><span class="typ">Step</span><span class="pln"> </span><span class="lit">9</span><span class="pun">/</span><span class="lit">11</span><span class="pln"> </span><span class="pun">:</span><span class="pln"> RUN mkdir </span><span class="pun">-</span><span class="pln">p </span><span class="pun">/</span><span class="pln">home</span><span class="pun">/</span><span class="pln">$user</span><span class="pun">/.</span><span class="pln">composer </span><span class="pun">&amp;&amp;</span><span class="pln">     chown </span><span class="pun">-</span><span class="pln">R $user</span><span class="pun">:</span><span class="pln">$user </span><span class="pun">/</span><span class="pln">home</span><span class="pun">/</span><span class="pln">$user
 </span><span class="pun">---&gt;</span><span class="pln"> </span><span class="typ">Running</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> </span><span class="lit">7ca8c0cb7f09</span><span class="pln">
</span><span class="typ">Removing</span><span class="pln"> intermediate container </span><span class="lit">7ca8c0cb7f09</span><span class="pln">
 </span><span class="pun">---&gt;</span><span class="pln"> </span><span class="lit">3d2ef9519a8e</span><span class="pln">
</span><span class="typ">Step</span><span class="pln"> </span><span class="lit">10</span><span class="pun">/</span><span class="lit">11</span><span class="pln"> </span><span class="pun">:</span><span class="pln"> WORKDIR </span><span class="pun">/</span><span class="kwd">var</span><span class="pun">/</span><span class="pln">www
 </span><span class="pun">---&gt;</span><span class="pln"> </span><span class="typ">Running</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> </span><span class="lit">4a964f91edfa</span><span class="pln">
</span><span class="typ">Removing</span><span class="pln"> intermediate container </span><span class="lit">4a964f91edfa</span><span class="pln">
 </span><span class="pun">---&gt;</span><span class="pln"> </span><span class="lit">00ada639da21</span><span class="pln">
</span><span class="typ">Step</span><span class="pln"> </span><span class="lit">11</span><span class="pun">/</span><span class="lit">11</span><span class="pln"> </span><span class="pun">:</span><span class="pln"> USER $user
 </span><span class="pun">---&gt;</span><span class="pln"> </span><span class="typ">Running</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> </span><span class="lit">9f8e874fede9</span><span class="pln">
</span><span class="typ">Removing</span><span class="pln"> intermediate container </span><span class="lit">9f8e874fede9</span><span class="pln">
 </span><span class="pun">---&gt;</span><span class="pln"> fe176ff4702b

</span><span class="typ">Successfully</span><span class="pln"> built fe176ff4702b
</span><span class="typ">Successfully</span><span class="pln"> tagged travellist</span><span class="pun">:</span><span class="pln">latest</span></pre>

<p>
	نُشغّل البيئة بالخلفية بعد الانتهاء من مرحلة الإنشاء بتنفيذ الأمر:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_6375_66" style="">
<span class="pln">docker</span><span class="pun">-</span><span class="pln">compose up </span><span class="pun">-</span><span class="pln">d</span></pre>

<p>
	ويكون الخرج على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_6375_68" style="">
<span class="typ">Creating</span><span class="pln"> travellist</span><span class="pun">-</span><span class="pln">db    </span><span class="pun">...</span><span class="pln"> </span><span class="kwd">done</span><span class="pln">
</span><span class="typ">Creating</span><span class="pln"> travellist</span><span class="pun">-</span><span class="pln">app   </span><span class="pun">...</span><span class="pln"> </span><span class="kwd">done</span><span class="pln">
</span><span class="typ">Creating</span><span class="pln"> travellist</span><span class="pun">-</span><span class="pln">nginx </span><span class="pun">...</span><span class="pln"> </span><span class="kwd">done</span></pre>

<p>
	نستعرض معلومات حالة الخدمة النشطة حاليًا بتنفيذ الأمر:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_6375_70" style="">
<span class="pln">docker</span><span class="pun">-</span><span class="pln">compose ps</span></pre>

<p>
	ويكون الخرج على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_6375_72" style="">
<span class="pln">     </span><span class="typ">Name</span><span class="pln">                    </span><span class="typ">Command</span><span class="pln">               </span><span class="typ">State</span><span class="pln">          </span><span class="typ">Ports</span><span class="pln">        
</span><span class="pun">--------------------------------------------------------------------------------</span><span class="pln">
travellist</span><span class="pun">-</span><span class="pln">app     docker</span><span class="pun">-</span><span class="pln">php</span><span class="pun">-</span><span class="pln">entrypoint php</span><span class="pun">-</span><span class="pln">fpm    </span><span class="typ">Up</span><span class="pln">      </span><span class="lit">9000</span><span class="pun">/</span><span class="pln">tcp            
travellist</span><span class="pun">-</span><span class="pln">db      docker</span><span class="pun">-</span><span class="pln">entrypoint</span><span class="pun">.</span><span class="pln">sh mysqld      </span><span class="typ">Up</span><span class="pln">      </span><span class="lit">3306</span><span class="pun">/</span><span class="pln">tcp</span><span class="pun">,</span><span class="pln"> </span><span class="lit">33060</span><span class="pun">/</span><span class="pln">tcp 
travellist</span><span class="pun">-</span><span class="pln">nginx   </span><span class="pun">/</span><span class="pln">docker</span><span class="pun">-</span><span class="pln">entrypoint</span><span class="pun">.</span><span class="pln">sh ngin </span><span class="pun">...</span><span class="pln">   </span><span class="typ">Up</span><span class="pln">      </span><span class="lit">0.0</span><span class="pun">.</span><span class="lit">0.0</span><span class="pun">:</span><span class="lit">8000</span><span class="pun">-&gt;</span><span class="lit">80</span><span class="pun">/</span><span class="pln">tcp</span></pre>

<p>
	أصبحت البيئة الآن جاهزة للعمل، ولكن يجب تنفيذ بعض الأوامر قبل انتهاء إعداد التطبيق كاملًا. نستخدم الأمر <code>docker-compose exec</code> لتنفيذ جميع الأوامر ضمن الحاويات، فلو رغبنا بتنفيذ الأمر <code>ls –l</code> الذي يظهر معلومات مفصلة حول الملفات في مسار التطبيق، ننفذ الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_6375_74" style="">
<span class="pln">docker</span><span class="pun">-</span><span class="pln">compose </span><span class="kwd">exec</span><span class="pln"> app ls </span><span class="pun">-</span><span class="pln">l</span></pre>

<p>
	وسيكون خرج شبيهًا بالخرج التالي:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_6375_83" style="">
<span class="pln">total </span><span class="lit">260</span><span class="pln">
</span><span class="pun">-</span><span class="pln">rw</span><span class="pun">-</span><span class="pln">rw</span><span class="pun">-</span><span class="pln">r</span><span class="pun">--</span><span class="pln">  </span><span class="lit">1</span><span class="pln"> sammy sammy    </span><span class="lit">737</span><span class="pln"> </span><span class="typ">Jun</span><span class="pln">  </span><span class="lit">9</span><span class="pln"> </span><span class="lit">11</span><span class="pun">:</span><span class="lit">19</span><span class="pln"> </span><span class="typ">Dockerfile</span><span class="pln">
</span><span class="pun">-</span><span class="pln">rw</span><span class="pun">-</span><span class="pln">rw</span><span class="pun">-</span><span class="pln">r</span><span class="pun">--</span><span class="pln">  </span><span class="lit">1</span><span class="pln"> sammy sammy    </span><span class="lit">101</span><span class="pln"> </span><span class="typ">Jan</span><span class="pln">  </span><span class="lit">7</span><span class="pln"> </span><span class="lit">08</span><span class="pun">:</span><span class="lit">05</span><span class="pln"> README</span><span class="pun">.</span><span class="pln">md
drwxrwxr</span><span class="pun">-</span><span class="pln">x  </span><span class="lit">6</span><span class="pln"> sammy sammy   </span><span class="lit">4096</span><span class="pln"> </span><span class="typ">Jan</span><span class="pln">  </span><span class="lit">7</span><span class="pln"> </span><span class="lit">08</span><span class="pun">:</span><span class="lit">05</span><span class="pln"> app
</span><span class="pun">-</span><span class="pln">rwxr</span><span class="pun">-</span><span class="pln">xr</span><span class="pun">-</span><span class="pln">x  </span><span class="lit">1</span><span class="pln"> sammy sammy   </span><span class="lit">1686</span><span class="pln"> </span><span class="typ">Jan</span><span class="pln">  </span><span class="lit">7</span><span class="pln"> </span><span class="lit">08</span><span class="pun">:</span><span class="lit">05</span><span class="pln"> artisan
drwxrwxr</span><span class="pun">-</span><span class="pln">x  </span><span class="lit">3</span><span class="pln"> sammy sammy   </span><span class="lit">4096</span><span class="pln"> </span><span class="typ">Jan</span><span class="pln">  </span><span class="lit">7</span><span class="pln"> </span><span class="lit">08</span><span class="pun">:</span><span class="lit">05</span><span class="pln"> bootstrap
</span><span class="pun">-</span><span class="pln">rw</span><span class="pun">-</span><span class="pln">rw</span><span class="pun">-</span><span class="pln">r</span><span class="pun">--</span><span class="pln">  </span><span class="lit">1</span><span class="pln"> sammy sammy   </span><span class="lit">1501</span><span class="pln"> </span><span class="typ">Jan</span><span class="pln">  </span><span class="lit">7</span><span class="pln"> </span><span class="lit">08</span><span class="pun">:</span><span class="lit">05</span><span class="pln"> composer</span><span class="pun">.</span><span class="pln">json
</span><span class="pun">-</span><span class="pln">rw</span><span class="pun">-</span><span class="pln">rw</span><span class="pun">-</span><span class="pln">r</span><span class="pun">--</span><span class="pln">  </span><span class="lit">1</span><span class="pln"> sammy sammy </span><span class="lit">179071</span><span class="pln"> </span><span class="typ">Jan</span><span class="pln">  </span><span class="lit">7</span><span class="pln"> </span><span class="lit">08</span><span class="pun">:</span><span class="lit">05</span><span class="pln"> composer</span><span class="pun">.</span><span class="kwd">lock</span><span class="pln">
drwxrwxr</span><span class="pun">-</span><span class="pln">x  </span><span class="lit">2</span><span class="pln"> sammy sammy   </span><span class="lit">4096</span><span class="pln"> </span><span class="typ">Jan</span><span class="pln">  </span><span class="lit">7</span><span class="pln"> </span><span class="lit">08</span><span class="pun">:</span><span class="lit">05</span><span class="pln"> config
drwxrwxr</span><span class="pun">-</span><span class="pln">x  </span><span class="lit">5</span><span class="pln"> sammy sammy   </span><span class="lit">4096</span><span class="pln"> </span><span class="typ">Jan</span><span class="pln">  </span><span class="lit">7</span><span class="pln"> </span><span class="lit">08</span><span class="pun">:</span><span class="lit">05</span><span class="pln"> database
drwxrwxr</span><span class="pun">-</span><span class="pln">x  </span><span class="lit">4</span><span class="pln"> sammy sammy   </span><span class="lit">4096</span><span class="pln"> </span><span class="typ">Jun</span><span class="pln">  </span><span class="lit">9</span><span class="pln"> </span><span class="lit">11</span><span class="pun">:</span><span class="lit">19</span><span class="pln"> docker</span><span class="pun">-</span><span class="pln">compose
</span><span class="pun">-</span><span class="pln">rw</span><span class="pun">-</span><span class="pln">rw</span><span class="pun">-</span><span class="pln">r</span><span class="pun">--</span><span class="pln">  </span><span class="lit">1</span><span class="pln"> sammy sammy    </span><span class="lit">965</span><span class="pln"> </span><span class="typ">Jun</span><span class="pln">  </span><span class="lit">9</span><span class="pln"> </span><span class="lit">11</span><span class="pun">:</span><span class="lit">27</span><span class="pln"> docker</span><span class="pun">-</span><span class="pln">compose</span><span class="pun">.</span><span class="pln">yml
</span><span class="pun">-</span><span class="pln">rw</span><span class="pun">-</span><span class="pln">rw</span><span class="pun">-</span><span class="pln">r</span><span class="pun">--</span><span class="pln">  </span><span class="lit">1</span><span class="pln"> sammy sammy   </span><span class="lit">1013</span><span class="pln"> </span><span class="typ">Jan</span><span class="pln">  </span><span class="lit">7</span><span class="pln"> </span><span class="lit">08</span><span class="pun">:</span><span class="lit">05</span><span class="pln"> </span><span class="kwd">package</span><span class="pun">.</span><span class="pln">json
</span><span class="pun">-</span><span class="pln">rw</span><span class="pun">-</span><span class="pln">rw</span><span class="pun">-</span><span class="pln">r</span><span class="pun">--</span><span class="pln">  </span><span class="lit">1</span><span class="pln"> sammy sammy   </span><span class="lit">1405</span><span class="pln"> </span><span class="typ">Jan</span><span class="pln">  </span><span class="lit">7</span><span class="pln"> </span><span class="lit">08</span><span class="pun">:</span><span class="lit">05</span><span class="pln"> phpunit</span><span class="pun">.</span><span class="pln">xml
drwxrwxr</span><span class="pun">-</span><span class="pln">x  </span><span class="lit">2</span><span class="pln"> sammy sammy   </span><span class="lit">4096</span><span class="pln"> </span><span class="typ">Jan</span><span class="pln">  </span><span class="lit">7</span><span class="pln"> </span><span class="lit">08</span><span class="pun">:</span><span class="lit">05</span><span class="pln"> </span><span class="kwd">public</span><span class="pln">
</span><span class="pun">-</span><span class="pln">rw</span><span class="pun">-</span><span class="pln">rw</span><span class="pun">-</span><span class="pln">r</span><span class="pun">--</span><span class="pln">  </span><span class="lit">1</span><span class="pln"> sammy sammy    </span><span class="lit">273</span><span class="pln"> </span><span class="typ">Jan</span><span class="pln">  </span><span class="lit">7</span><span class="pln"> </span><span class="lit">08</span><span class="pun">:</span><span class="lit">05</span><span class="pln"> readme</span><span class="pun">.</span><span class="pln">md
drwxrwxr</span><span class="pun">-</span><span class="pln">x  </span><span class="lit">6</span><span class="pln"> sammy sammy   </span><span class="lit">4096</span><span class="pln"> </span><span class="typ">Jan</span><span class="pln">  </span><span class="lit">7</span><span class="pln"> </span><span class="lit">08</span><span class="pun">:</span><span class="lit">05</span><span class="pln"> resources
drwxrwxr</span><span class="pun">-</span><span class="pln">x  </span><span class="lit">2</span><span class="pln"> sammy sammy   </span><span class="lit">4096</span><span class="pln"> </span><span class="typ">Jan</span><span class="pln">  </span><span class="lit">7</span><span class="pln"> </span><span class="lit">08</span><span class="pun">:</span><span class="lit">05</span><span class="pln"> routes
</span><span class="pun">-</span><span class="pln">rw</span><span class="pun">-</span><span class="pln">rw</span><span class="pun">-</span><span class="pln">r</span><span class="pun">--</span><span class="pln">  </span><span class="lit">1</span><span class="pln"> sammy sammy    </span><span class="lit">563</span><span class="pln"> </span><span class="typ">Jan</span><span class="pln">  </span><span class="lit">7</span><span class="pln"> </span><span class="lit">08</span><span class="pun">:</span><span class="lit">05</span><span class="pln"> server</span><span class="pun">.</span><span class="pln">php
drwxrwxr</span><span class="pun">-</span><span class="pln">x  </span><span class="lit">5</span><span class="pln"> sammy sammy   </span><span class="lit">4096</span><span class="pln"> </span><span class="typ">Jan</span><span class="pln">  </span><span class="lit">7</span><span class="pln"> </span><span class="lit">08</span><span class="pun">:</span><span class="lit">05</span><span class="pln"> storage
drwxrwxr</span><span class="pun">-</span><span class="pln">x  </span><span class="lit">4</span><span class="pln"> sammy sammy   </span><span class="lit">4096</span><span class="pln"> </span><span class="typ">Jan</span><span class="pln">  </span><span class="lit">7</span><span class="pln"> </span><span class="lit">08</span><span class="pun">:</span><span class="lit">05</span><span class="pln"> tests
drwxrwxr</span><span class="pun">-</span><span class="pln">x </span><span class="lit">41</span><span class="pln"> sammy sammy   </span><span class="lit">4096</span><span class="pln"> </span><span class="typ">Jun</span><span class="pln">  </span><span class="lit">9</span><span class="pln"> </span><span class="lit">11</span><span class="pun">:</span><span class="lit">32</span><span class="pln"> vendor
</span><span class="pun">-</span><span class="pln">rw</span><span class="pun">-</span><span class="pln">rw</span><span class="pun">-</span><span class="pln">r</span><span class="pun">--</span><span class="pln">  </span><span class="lit">1</span><span class="pln"> sammy sammy    </span><span class="lit">538</span><span class="pln"> </span><span class="typ">Jan</span><span class="pln">  </span><span class="lit">7</span><span class="pln"> </span><span class="lit">08</span><span class="pun">:</span><span class="lit">05</span><span class="pln"> webpack</span><span class="pun">.</span><span class="pln">mix</span><span class="pun">.</span><span class="pln">js</span></pre>

<p>
	نشغّل الأداة <code>composer install</code> لتثبيت اعتماديات التطبيق كما يلي:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_6375_85" style="">
<span class="pln">docker</span><span class="pun">-</span><span class="pln">compose </span><span class="kwd">exec</span><span class="pln"> app composer install</span></pre>

<p>
	يظهر الخرج التالي بعد إتمام العملية:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_6375_87" style="">
<span class="typ">Loading</span><span class="pln"> composer repositories </span><span class="kwd">with</span><span class="pln"> </span><span class="kwd">package</span><span class="pln"> information
</span><span class="typ">Installing</span><span class="pln"> dependencies </span><span class="pun">(</span><span class="pln">including </span><span class="kwd">require</span><span class="pun">-</span><span class="pln">dev</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">from</span><span class="pln"> </span><span class="kwd">lock</span><span class="pln"> file
</span><span class="typ">Package</span><span class="pln"> operations</span><span class="pun">:</span><span class="pln"> </span><span class="lit">85</span><span class="pln"> installs</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pln"> updates</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pln"> removals
  </span><span class="pun">-</span><span class="pln"> </span><span class="typ">Installing</span><span class="pln"> doctrine</span><span class="pun">/</span><span class="pln">inflector </span><span class="pun">(</span><span class="lit">1.3</span><span class="pun">.</span><span class="lit">1</span><span class="pun">):</span><span class="pln"> </span><span class="typ">Downloading</span><span class="pln"> </span><span class="pun">(</span><span class="lit">100</span><span class="pun">%)</span><span class="pln">         
  </span><span class="pun">-</span><span class="pln"> </span><span class="typ">Installing</span><span class="pln"> doctrine</span><span class="pun">/</span><span class="pln">lexer </span><span class="pun">(</span><span class="lit">1.2</span><span class="pun">.</span><span class="lit">0</span><span class="pun">):</span><span class="pln"> </span><span class="typ">Downloading</span><span class="pln"> </span><span class="pun">(</span><span class="lit">100</span><span class="pun">%)</span><span class="pln">         
  </span><span class="pun">-</span><span class="pln"> </span><span class="typ">Installing</span><span class="pln"> dragonmantank</span><span class="pun">/</span><span class="pln">cron</span><span class="pun">-</span><span class="pln">expression </span><span class="pun">(</span><span class="pln">v2</span><span class="pun">.</span><span class="lit">3.0</span><span class="pun">):</span><span class="pln"> </span><span class="typ">Downloading</span><span class="pln"> </span><span class="pun">(</span><span class="lit">100</span><span class="pun">%)</span><span class="pln">         
  </span><span class="pun">-</span><span class="pln"> </span><span class="typ">Installing</span><span class="pln"> erusev</span><span class="pun">/</span><span class="pln">parsedown </span><span class="pun">(</span><span class="lit">1.7</span><span class="pun">.</span><span class="lit">4</span><span class="pun">):</span><span class="pln"> </span><span class="typ">Downloading</span><span class="pln"> </span><span class="pun">(</span><span class="lit">100</span><span class="pun">%)</span><span class="pln">         
  </span><span class="pun">-</span><span class="pln"> </span><span class="typ">Installing</span><span class="pln"> symfony</span><span class="pun">/</span><span class="pln">polyfill</span><span class="pun">-</span><span class="pln">ctype </span><span class="pun">(</span><span class="pln">v1</span><span class="pun">.</span><span class="lit">13.1</span><span class="pun">):</span><span class="pln"> </span><span class="typ">Downloading</span><span class="pln"> </span><span class="pun">(</span><span class="lit">100</span><span class="pun">%)</span><span class="pln">         
  </span><span class="pun">-</span><span class="pln"> </span><span class="typ">Installing</span><span class="pln"> phpoption</span><span class="pun">/</span><span class="pln">phpoption </span><span class="pun">(</span><span class="lit">1.7</span><span class="pun">.</span><span class="lit">2</span><span class="pun">):</span><span class="pln"> </span><span class="typ">Downloading</span><span class="pln"> </span><span class="pun">(</span><span class="lit">100</span><span class="pun">%)</span><span class="pln">         
  </span><span class="pun">-</span><span class="pln"> </span><span class="typ">Installing</span><span class="pln"> vlucas</span><span class="pun">/</span><span class="pln">phpdotenv </span><span class="pun">(</span><span class="pln">v3</span><span class="pun">.</span><span class="lit">6.0</span><span class="pun">):</span><span class="pln"> </span><span class="typ">Downloading</span><span class="pln"> </span><span class="pun">(</span><span class="lit">100</span><span class="pun">%)</span><span class="pln">         
  </span><span class="pun">-</span><span class="pln"> </span><span class="typ">Installing</span><span class="pln"> symfony</span><span class="pun">/</span><span class="pln">css</span><span class="pun">-</span><span class="pln">selector </span><span class="pun">(</span><span class="pln">v5</span><span class="pun">.</span><span class="lit">0.2</span><span class="pun">):</span><span class="pln"> </span><span class="typ">Downloading</span><span class="pln"> </span><span class="pun">(</span><span class="lit">100</span><span class="pun">%)</span><span class="pln">             
</span><span class="pun">…</span><span class="pln">
</span><span class="typ">Generating</span><span class="pln"> optimized autoload files
</span><span class="pun">&gt;</span><span class="pln"> </span><span class="typ">Illuminate</span><span class="pln">\Foundation\ComposerScripts</span><span class="pun">::</span><span class="pln">postAutoloadDump
</span><span class="pun">&gt;</span><span class="pln"> </span><span class="lit">@php</span><span class="pln"> artisan </span><span class="kwd">package</span><span class="pun">:</span><span class="pln">discover </span><span class="pun">--</span><span class="pln">ansi
</span><span class="typ">Discovered</span><span class="pln"> </span><span class="typ">Package</span><span class="pun">:</span><span class="pln"> facade</span><span class="pun">/</span><span class="pln">ignition
</span><span class="typ">Discovered</span><span class="pln"> </span><span class="typ">Package</span><span class="pun">:</span><span class="pln"> fideloper</span><span class="pun">/</span><span class="pln">proxy
</span><span class="typ">Discovered</span><span class="pln"> </span><span class="typ">Package</span><span class="pun">:</span><span class="pln"> laravel</span><span class="pun">/</span><span class="pln">tinker
</span><span class="typ">Discovered</span><span class="pln"> </span><span class="typ">Package</span><span class="pun">:</span><span class="pln"> nesbot</span><span class="pun">/</span><span class="pln">carbon
</span><span class="typ">Discovered</span><span class="pln"> </span><span class="typ">Package</span><span class="pun">:</span><span class="pln"> nunomaduro</span><span class="pun">/</span><span class="pln">collision
</span><span class="typ">Package</span><span class="pln"> manifest generated successfully</span><span class="pun">.</span></pre>

<p>
	يجب أن نولد مفتاح التطبيق الفريد باستخدام الأداة <code>artisan</code> الخاصة بلارافيل قبل أن نختبر التطبيق الذي طورناه، إذ يشفّر هذا المفتاح جلسات المستخدم والبيانات الحساسة الأخرى.
</p>

<p>
	ننفذ الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_6375_89" style="">
<span class="pln">docker</span><span class="pun">-</span><span class="pln">compose </span><span class="kwd">exec</span><span class="pln"> app php artisan key</span><span class="pun">:</span><span class="pln">generate</span></pre>

<p>
	ويكون الخرج على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_6375_91" style="">
<span class="typ">Application</span><span class="pln"> key </span><span class="kwd">set</span><span class="pln"> successfully</span><span class="pun">.</span></pre>

<p>
	نطلب الرابط التالي من المتصفح:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_6375_93" style="">
<span class="pln">http</span><span class="pun">:</span><span class="com">//server_domain_or_IP:8000</span></pre>

<p>
	<strong>ملاحظة</strong>: نطلب العنوان المحلي "http://localhost:8000" في حالة العمل على حاسب محلي ضمن المتصفح لتشغيل التطبيق التوضيحي الذي نطوّره.
</p>

<p>
	يظهر عندها الخرج التالي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="108936" href="https://academy.hsoub.com/uploads/monthly_2022_10/travellist_docker.png.c77c4121d9028ee47135dcea337978e8.png" rel=""><img alt="travellist_docker.png" class="ipsImage ipsImage_thumbnailed" data-fileid="108936" data-unique="49sbioa2h" src="https://academy.hsoub.com/uploads/monthly_2022_10/travellist_docker.png.c77c4121d9028ee47135dcea337978e8.png"></a>
</p>

<p>
	نستطيع استخدام الأمر <code>logs</code> لفحص السجلات التي تولّدها الخدمة بتنفيذ الأمر التالي:
</p>

<pre class="ipsCode">
docker-compose logs nginx
</pre>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_6375_97" style="">
<span class="typ">Attaching</span><span class="pln"> to travellist</span><span class="pun">-</span><span class="pln">nginx
</span><span class="pun">…</span><span class="pln">
travellist</span><span class="pun">-</span><span class="pln">nginx </span><span class="pun">|</span><span class="pln"> </span><span class="str">/docker-entrypoint.sh: Launching /</span><span class="pln">docker</span><span class="pun">-</span><span class="pln">entrypoint</span><span class="pun">.</span><span class="pln">d</span><span class="pun">/</span><span class="lit">20</span><span class="pun">-</span><span class="pln">envsubst</span><span class="pun">-</span><span class="pln">on</span><span class="pun">-</span><span class="pln">templates</span><span class="pun">.</span><span class="pln">sh
travellist</span><span class="pun">-</span><span class="pln">nginx </span><span class="pun">|</span><span class="pln"> </span><span class="pun">/</span><span class="pln">docker</span><span class="pun">-</span><span class="pln">entrypoint</span><span class="pun">.</span><span class="pln">sh</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Configuration</span><span class="pln"> complete</span><span class="pun">;</span><span class="pln"> ready </span><span class="kwd">for</span><span class="pln"> start up
travellist</span><span class="pun">-</span><span class="pln">nginx </span><span class="pun">|</span><span class="pln"> </span><span class="lit">192.168</span><span class="pun">.</span><span class="lit">0.1</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">[</span><span class="lit">09</span><span class="pun">/</span><span class="typ">Jun</span><span class="pun">/</span><span class="lit">2020</span><span class="pun">:</span><span class="lit">11</span><span class="pun">:</span><span class="lit">46</span><span class="pun">:</span><span class="lit">34</span><span class="pln"> </span><span class="pun">+</span><span class="lit">0000</span><span class="pun">]</span><span class="pln"> </span><span class="str">"GET / HTTP/1.1"</span><span class="pln"> </span><span class="lit">200</span><span class="pln"> </span><span class="lit">627</span><span class="pln"> </span><span class="str">"-"</span><span class="pln"> </span><span class="str">"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"</span><span class="pln">
travellist</span><span class="pun">-</span><span class="pln">nginx </span><span class="pun">|</span><span class="pln"> </span><span class="lit">192.168</span><span class="pun">.</span><span class="lit">0.1</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">[</span><span class="lit">09</span><span class="pun">/</span><span class="typ">Jun</span><span class="pun">/</span><span class="lit">2020</span><span class="pun">:</span><span class="lit">11</span><span class="pun">:</span><span class="lit">46</span><span class="pun">:</span><span class="lit">35</span><span class="pln"> </span><span class="pun">+</span><span class="lit">0000</span><span class="pun">]</span><span class="pln"> </span><span class="str">"GET / HTTP/1.1"</span><span class="pln"> </span><span class="lit">200</span><span class="pln"> </span><span class="lit">627</span><span class="pln"> </span><span class="str">"-"</span><span class="pln"> </span><span class="str">"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"</span><span class="pln">
</span><span class="pun">…</span></pre>

<p>
	نستطيع إيقاف العمل مؤقتًا لبيئة دوكر كومبوز مع الحفاظ على حالة الخدمات بتشغيل الأمر:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_6375_99" style="">
<span class="pln">docker</span><span class="pun">-</span><span class="pln">compose pause</span></pre>

<p>
	ليكون الخرج على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_6375_101" style="">
<span class="typ">Pausing</span><span class="pln"> travellist</span><span class="pun">-</span><span class="pln">db    </span><span class="pun">...</span><span class="pln"> </span><span class="kwd">done</span><span class="pln">
</span><span class="typ">Pausing</span><span class="pln"> travellist</span><span class="pun">-</span><span class="pln">nginx </span><span class="pun">...</span><span class="pln"> </span><span class="kwd">done</span><span class="pln">
</span><span class="typ">Pausing</span><span class="pln"> travellist</span><span class="pun">-</span><span class="pln">app   </span><span class="pun">...</span><span class="pln"> </span><span class="kwd">done</span></pre>

<p>
	نستطيع استئناف العمل بتنفيذ الأمر:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_6375_103" style="">
<span class="pln">docker</span><span class="pun">-</span><span class="pln">compose unpause</span></pre>

<p>
	ويكون الخرج على النحو التالي:
</p>

<pre class="ipsCode">
Unpausing travellist-app   ... done
Unpausing travellist-nginx ... done
Unpausing travellist-db    ... done
</pre>

<p>
	نستطيع إيقاف عمل بيئة دوكر كومبوز ونزيل جميع حاوياتها وشبكاتها ووحدات تخزينها بتنفيذ الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_6375_105" style="">
<span class="pln">docker</span><span class="pun">-</span><span class="pln">compose down</span></pre>

<p>
	ويكون الخرج على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_6375_107" style="">
<span class="typ">Stopping</span><span class="pln"> travellist</span><span class="pun">-</span><span class="pln">nginx </span><span class="pun">...</span><span class="pln"> </span><span class="kwd">done</span><span class="pln">
</span><span class="typ">Stopping</span><span class="pln"> travellist</span><span class="pun">-</span><span class="pln">db    </span><span class="pun">...</span><span class="pln"> </span><span class="kwd">done</span><span class="pln">
</span><span class="typ">Stopping</span><span class="pln"> travellist</span><span class="pun">-</span><span class="pln">app   </span><span class="pun">...</span><span class="pln"> </span><span class="kwd">done</span><span class="pln">
</span><span class="typ">Removing</span><span class="pln"> travellist</span><span class="pun">-</span><span class="pln">nginx </span><span class="pun">...</span><span class="pln"> </span><span class="kwd">done</span><span class="pln">
</span><span class="typ">Removing</span><span class="pln"> travellist</span><span class="pun">-</span><span class="pln">db    </span><span class="pun">...</span><span class="pln"> </span><span class="kwd">done</span><span class="pln">
</span><span class="typ">Removing</span><span class="pln"> travellist</span><span class="pun">-</span><span class="pln">app   </span><span class="pun">...</span><span class="pln"> </span><span class="kwd">done</span><span class="pln">
</span><span class="typ">Removing</span><span class="pln"> network travellist</span><span class="pun">-</span><span class="pln">laravel</span><span class="pun">-</span><span class="pln">demo_travellist</span></pre>

<h2>
	الخاتمة
</h2>

<p>
	أعددنا بيئة دوكر بثلاث حاويات باستخدام أداة دوكر كومبوز Docker Compose وحددنا بنيته الأساسية في ملف YAML. يمكن العمل على تطبيق لارافيل Laravel دون الحاجة إلى <a href="https://academy.hsoub.com/devops/servers/%D8%AF%D9%84%D9%8A%D9%84-%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%AE%D8%A7%D8%AF%D9%85-%D9%88%D9%8A%D8%A8-%D9%85%D8%AD%D9%84%D9%8A-%D8%AE%D8%B7%D9%88%D8%A9-%D8%A8%D8%AE%D8%B7%D9%88%D8%A9-r422/" rel="">تثبيت خادم ويب محلي</a> وإعداده للتطوير والاختبار، ويتحقق كل ذلك باستخدام بيئة معزولة يمكن تكرارها وإعادة إنشائها بسهولة حتى الوصول إلى التطبيق المطلوب تثبيته ضمن بيئات الإنتاج.
</p>

<p>
	ترجمة وبتصرف للمقال <a href="https://www.digitalocean.com/community/tutorials/how-to-install-and-set-up-laravel-with-docker-compose-on-ubuntu-20-04" rel="external nofollow">How To Install and Set Up Laravel with Docker Compose on Ubuntu 20.04</a> لصاحبه Erika Heidi.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%AF%D9%88%D9%83%D8%B1-docker-r607/" rel="">مدخل إلى دوكر Docker</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/php/laravel/%D9%86%D8%B4%D8%B1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-laravel-%D8%B9%D9%84%D9%89-%D9%85%D9%86%D8%B5%D8%A9-heroku-r357/" rel="">نشر تطبيقات Laravel على منصة Heroku</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/php/laravel/%d8%aa%d8%ab%d8%a8%d9%8a%d8%aa-laravel-5-%d9%88%d8%a5%d8%b9%d8%af%d8%a7%d8%af%d9%87-%d8%b9%d9%84%d9%89-windows-%d9%88ubuntu-r212/" rel="">تثبيت Laravel 5 وإعداده على Windows وUbuntu</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">650</guid><pubDate>Mon, 03 Oct 2022 17:03:05 +0000</pubDate></item><item><title>&#x623;&#x633;&#x627;&#x633;&#x64A;&#x627;&#x62A; &#x62A;&#x646;&#x633;&#x64A;&#x642; &#x627;&#x644;&#x62D;&#x627;&#x648;&#x64A;&#x627;&#x62A;</title><link>https://academy.hsoub.com/devops/cloud-computing/docker/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D8%AA%D9%86%D8%B3%D9%8A%D9%82-%D8%A7%D9%84%D8%AD%D8%A7%D9%88%D9%8A%D8%A7%D8%AA-r643/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_08/62fb6cf5d4ea4_---.png.d7b5eebbfff323083cec65ea741cd84b.png" /></p>

<p>
	نتعرف في هذا المقال على وضع تطبيق ويب بأكمله مع جميع خدماته ضمن <a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%AF%D9%88%D9%83%D8%B1-docker-r607/" rel="">حاويات دوكر Docker</a> وتنسيقها وضبط إعداداتها.
</p>

<h2>
	استخدام الحاويات مع React
</h2>

<p>
	سنحاول أن ننشئ تطبيق <a href="https://academy.hsoub.com/programming/javascript/react/%D9%85%D8%A7-%D9%87%D9%8A-react%D8%9F-r773/" rel="">React</a> ونضعه ضمن حاوية تاليًا، لهذا سنختار <a href="https://academy.hsoub.com/programming/javascript/%D9%81%D9%8A%D8%AF%D9%8A%D9%88-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%85%D8%AF%D9%8A%D8%B1-%D8%A7%D9%84%D8%AD%D8%B2%D9%85-npm-r1225/" rel="">npm</a> مديرَا للحزم علمًا أن yarn هو المدير الافتراضي لبرنامج create-react-app:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5120_7" style="">
<span class="pln">$ npx create</span><span class="pun">-</span><span class="pln">react</span><span class="pun">-</span><span class="pln">app hello</span><span class="pun">-</span><span class="pln">front </span><span class="pun">--</span><span class="pln">use</span><span class="pun">-</span><span class="pln">npm
  </span><span class="pun">...</span><span class="pln">

  </span><span class="typ">Happy</span><span class="pln"> hacking</span><span class="pun">!</span></pre>

<p>
	يُثبت create-react-app كل الاعتماديات اللازمة، فلا حاجة لتنفيذ الأمر <code>npm install</code>. ستكون الخطوة الثانية تحويل <a href="https://academy.hsoub.com/programming/javascript/%D9%86%D9%85%D8%B7-%D9%83%D8%AA%D8%A7%D8%A8%D8%A9-%D8%B4%D9%8A%D9%81%D8%B1%D8%A9-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r785/" rel="">شيفرة JavaScript</a> و <a href="https://wiki.hsoub.com/CSS" rel="external">CSS</a> إلى ملفات ساكنة جاهزة لمرحلة الإنتاج. أمّا create-react-app فلديه <code>build</code> ليكون سكربت npm، لهذا سنستفيد من هذه الناحية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5120_16" style="">
<span class="pln">$ npm run build
  </span><span class="pun">...</span><span class="pln">

  </span><span class="typ">Creating</span><span class="pln"> an optimized production build</span><span class="pun">...</span><span class="pln">
  </span><span class="pun">...</span><span class="pln">
  </span><span class="typ">The</span><span class="pln"> build folder is ready to be deployed</span><span class="pun">.</span><span class="pln">
  </span><span class="pun">...</span></pre>

<p>
	أما الخطوة الأخيرة هي التفكير بطريقة للعمل مع خادم لتقديم الملفات الساكنة. يمكننا الاستفادة من <a href="https://expressjs.com/en/starter/static-files.html" rel="external nofollow">express.static</a> مع خادم Express لهذا الغرض، لهذا سأترك الموضوع تمرينًا لك، وسننتقل بدلًا من ذلك إلى كتابة ملف Dockerfile:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5120_18" style="">
<span class="pln">FROM node</span><span class="pun">:</span><span class="lit">16</span><span class="pln">

WORKDIR </span><span class="pun">/</span><span class="pln">usr</span><span class="pun">/</span><span class="pln">src</span><span class="pun">/</span><span class="pln">app

COPY </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln">

RUN npm ci

RUN npm run build</span></pre>

<p>
	يبدو ما كتبناه صحيحًا تقريبًا، لهذا سنبنيه ونقيّم مسارنا، والهدف أن نبني التمرين دون أخطاء. سنحاول بعد ذلك أن نتحقق من وجود الملفات داخل الحاوية من خلال أوامر "<a href="https://wiki.hsoub.com/Bash" rel="external">bash</a>".
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5120_21" style="">
<span class="pln">$ docker build </span><span class="pun">.</span><span class="pln"> </span><span class="pun">-</span><span class="pln">t hello</span><span class="pun">-</span><span class="pln">front
  </span><span class="pun">[+]</span><span class="pln"> </span><span class="typ">Building</span><span class="pln"> </span><span class="lit">172.4s</span><span class="pln"> </span><span class="pun">(</span><span class="lit">10</span><span class="pun">/</span><span class="lit">10</span><span class="pun">)</span><span class="pln"> FINISHED 

$ docker run </span><span class="pun">-</span><span class="pln">it hello</span><span class="pun">-</span><span class="pln">front bash

root@98fa9483ee85</span><span class="pun">:</span><span class="str">/usr/</span><span class="pln">src</span><span class="pun">/</span><span class="pln">app</span><span class="pun">#</span><span class="pln"> ls
  </span><span class="typ">Dockerfile</span><span class="pln">  README</span><span class="pun">.</span><span class="pln">md  build  node_modules  package</span><span class="pun">-</span><span class="pln">lock</span><span class="pun">.</span><span class="pln">json  package</span><span class="pun">.</span><span class="pln">json  </span><span class="kwd">public</span><span class="pln">  src

root@98fa9483ee85</span><span class="pun">:</span><span class="str">/usr/</span><span class="pln">src</span><span class="pun">/</span><span class="pln">app</span><span class="pun">#</span><span class="pln"> ls build</span><span class="pun">/</span><span class="pln">
  asset</span><span class="pun">-</span><span class="pln">manifest</span><span class="pun">.</span><span class="pln">json  favicon</span><span class="pun">.</span><span class="pln">ico  index</span><span class="pun">.</span><span class="pln">html  logo192</span><span class="pun">.</span><span class="pln">png  logo512</span><span class="pun">.</span><span class="pln">png  manifest</span><span class="pun">.</span><span class="pln">json  robots</span><span class="pun">.</span><span class="pln">txt  </span><span class="kwd">static</span></pre>

<p>
	الخيار الصحيح لتخديم الملفات الساكنة ضمن الحاوية هو <a href="https://www.npmjs.com/package/serve" rel="external nofollow">serve</a> نظرًا لوجود Node ضمن الحاوية. لنحاول تثبيت serve لتخديم الملفات الساكنة ونحن داخل الحاوية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5120_23" style="">
<span class="pln">root@98fa9483ee85</span><span class="pun">:</span><span class="str">/usr/</span><span class="pln">src</span><span class="pun">/</span><span class="pln">app</span><span class="pun">#</span><span class="pln"> npm install </span><span class="pun">-</span><span class="pln">g serve

  added </span><span class="lit">88</span><span class="pln"> packages</span><span class="pun">,</span><span class="pln"> and audited </span><span class="lit">89</span><span class="pln"> packages in </span><span class="lit">6s</span><span class="pln">

root@98fa9483ee85</span><span class="pun">:</span><span class="str">/usr/</span><span class="pln">src</span><span class="pun">/</span><span class="pln">app</span><span class="pun">#</span><span class="pln"> serve build

   </span><span class="pun">┌───────────────────────────────────┐</span><span class="pln">
   </span><span class="pun">│</span><span class="pln">                                   </span><span class="pun">│</span><span class="pln">
   </span><span class="pun">│</span><span class="pln">   </span><span class="typ">Serving</span><span class="pun">!</span><span class="pln">                        </span><span class="pun">│</span><span class="pln">
   </span><span class="pun">│</span><span class="pln">                                   </span><span class="pun">│</span><span class="pln">
   </span><span class="pun">│</span><span class="pln">   </span><span class="typ">Local</span><span class="pun">:</span><span class="pln">  http</span><span class="pun">:</span><span class="com">//localhost:5000   │</span><span class="pln">
   </span><span class="pun">│</span><span class="pln">                                   </span><span class="pun">│</span><span class="pln">
   </span><span class="pun">└───────────────────────────────────┘</span></pre>

<p>
	أغلق الحاوية باستخدام الاختصار "Ctrl+C" لإضافة بعض التوجيهات إلى ملف Dockerfile.
</p>

<p>
	يتحوّل تثبيت serve إلى عملية تشغيل <code>RUN</code> في ملف Dockerfile، وهكذا ستُثبّت الاعتمادية أثناء عملية البناء، وسيكون أمر تخديم مجلد البناء هو نفسه أمر تشغيل الحاوية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5120_26" style="">
<span class="pln">FROM node</span><span class="pun">:</span><span class="lit">16</span><span class="pln">

WORKDIR </span><span class="pun">/</span><span class="pln">usr</span><span class="pun">/</span><span class="pln">src</span><span class="pun">/</span><span class="pln">app

COPY </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln">

RUN npm ci

RUN npm run build

RUN npm install </span><span class="pun">-</span><span class="pln">g serve
CMD </span><span class="pun">[</span><span class="str">"serve"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"build"</span><span class="pun">]</span></pre>

<p>
	يتضمن الأمر <code>‍‍CMD</code> الآن قوسين مربعين ونكون بذلك قد استخدمنا ما يُعرف بالشكل التنفيذي exec form من الأمر <code>‍‍CMD</code>، علمًا أنه يُكتب <a href="https://docs.docker.com/engine/reference/builder/#cmd" rel="external nofollow">بثلاثة أشكال</a> لكن الشكل السابق هو المُفضّل.
</p>

<p>
	عندما نبني الصورة الآن باستخدام الأمر:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5120_28" style="">
<span class="pln">docker build </span><span class="pun">.</span><span class="pln"> </span><span class="pun">-</span><span class="pln">t hello</span><span class="pun">-</span><span class="pln">front</span></pre>

<p>
	ثم تشغيلها باستخدام الأمر:
</p>

<pre class="ipsCode">
docker run -p 5000:3000 hello-front
</pre>

<p>
	سيكون التطبيق متاحًا على العنوان "http://localhost:5000".
</p>

<h2>
	استخدام المراحل المتعددة
</h2>

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

<p>
	صُمّمت عملية البناء متعددة المراحل <a href="https://docs.docker.com/develop/develop-images/multistage-build/" rel="external nofollow">Multi-stage builds</a> لتفصل عملية البناء إلى مراحل مختلفة، ومن الممكن حينها تحديد ملفات الصورة التي يُسمح لها بالانتقال من مرحلة إلى أخرى، كما يتيح ذلك أيضًا إمكانية التحكم بحجم الصورة، فلن نحتاج إلى جميع الملفات الجانبية التي تنتج عن عملية البناء ضمن الصورة الناتجة. إنّ الصورة الأصغر أسرع في التحميل والتنزيل وتساعد في تخفيض عدد نقاط الضعف في برنامجك.
</p>

<p>
	عند استخدام البناء متعدد المراحل، بالإمكان الاعتماد على حلول تتبع نهج المحاولة والخطأ مثل استخدام الخادم <a href="https://en.wikipedia.org/wiki/Nginx" rel="external nofollow">Nginx</a> في إدارة الملفات الساكنة دون عناء شديد. تدلنا صفحة استخدام <a href="https://hub.docker.com/_/nginx" rel="external nofollow">Nginx مع Docker Hub</a> على المعلومات الضرورية لفتح المنافذ واستضافة محتوى ساكن.
</p>

<p>
	لنستخدم الآن ملف Dockerfile السابق بعد تغيير ما يلي التعليمة <code>FROM</code> لإضافة اسم المرحلة:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5120_30" style="">
<span class="pun">#</span><span class="pln"> called build</span><span class="pun">-</span><span class="pln">stage </span><span class="pun">الأولى</span><span class="pln"> </span><span class="pun">الآن</span><span class="pln"> </span><span class="pun">إلى</span><span class="pln"> </span><span class="pun">مرحلة</span><span class="pln"> </span><span class="pun">تُدعى</span><span class="pln"> FROM </span><span class="pun">تشير</span><span class="pln"> </span><span class="pun">تعليمة</span><span class="pln">  
FROM node</span><span class="pun">:</span><span class="lit">16</span><span class="pln"> AS build</span><span class="pun">-</span><span class="pln">stage
WORKDIR </span><span class="pun">/</span><span class="pln">usr</span><span class="pun">/</span><span class="pln">src</span><span class="pun">/</span><span class="pln">app

COPY </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln">

RUN npm ci

RUN npm run build
</span><span class="pun">#</span><span class="pln"> </span><span class="pun">هذه</span><span class="pln"> </span><span class="pun">مرحلة</span><span class="pln"> </span><span class="pun">جديدة</span><span class="pln"> </span><span class="pun">الآن،</span><span class="pln"> </span><span class="pun">وسيختفي</span><span class="pln"> </span><span class="pun">كل</span><span class="pln"> </span><span class="pun">شيء</span><span class="pln"> </span><span class="pun">قبلها</span><span class="pln"> </span><span class="pun">ما</span><span class="pln"> </span><span class="pun">عدا</span><span class="pln"> </span><span class="pun">الملفات</span><span class="pln"> </span><span class="pun">التي</span><span class="pln"> </span><span class="pun">نريد</span><span class="pln"> </span><span class="pun">نسخها</span><span class="pln">
FROM nginx</span><span class="pun">:</span><span class="lit">1.20</span><span class="pun">-</span><span class="pln">alpine
</span><span class="pun">#</span><span class="pln"> </span><span class="str">/usr/</span><span class="pln">share</span><span class="pun">/</span><span class="pln">nginx</span><span class="pun">/</span><span class="pln">html </span><span class="pun">إلى</span><span class="pln"> build</span><span class="pun">-</span><span class="pln">stage </span><span class="pun">نسخ</span><span class="pln"> </span><span class="pun">مجلد</span><span class="pln"> </span><span class="pun">البناء</span><span class="pln"> </span><span class="pun">من</span><span class="pln"> 
</span><span class="pun">#</span><span class="pln"> docker hub page </span><span class="pun">وجد</span><span class="pln"> </span><span class="pun">الموقع</span><span class="pln"> </span><span class="pun">الوجهة</span><span class="pln"> </span><span class="pun">من</span><span class="pln"> </span><span class="pun">خلال</span><span class="pln"> </span><span class="pun">صفحة</span><span class="pln"> </span><span class="pun">شروحات</span><span class="pln"> 
COPY </span><span class="pun">--</span><span class="pln">from</span><span class="pun">=</span><span class="pln">build</span><span class="pun">-</span><span class="pln">stage </span><span class="pun">/</span><span class="pln">usr</span><span class="pun">/</span><span class="pln">src</span><span class="pun">/</span><span class="pln">app</span><span class="pun">/</span><span class="pln">build </span><span class="pun">/</span><span class="pln">usr</span><span class="pun">/</span><span class="pln">share</span><span class="pun">/</span><span class="pln">nginx</span><span class="pun">/</span><span class="pln">html</span></pre>

<p>
	لقد صرحنا أيضًا عن مرحلة أخرى تُنقل إليها فقط الملفات الضرورية من المرحلة الأولى (المجلد "build" الذي يضم المحتوى الساكن).
</p>

<p>
	بعد بنائها ثانيةً، ستكون الصورة جاهزة لتخديم المحتوى الساكن. إن المنفذ الافتراضي لخادم Nginx هو 80 وبالتالي سينفع تنفيذ الأمر <code>p 8000:80-</code>، لهذا لا بد من تغيير معاملات أمر التشغيل قليلًا.
</p>

<p>
	تنطوي عملية البناء متعددة المراحل على تحسينات داخلية قد تؤثر على عملية البناء، إذ تتجاوز عملية البناء متعددة المراحل مثلًا المراحل التي لم تُستخدم، وبالتالي علينا تمرير بعض البيانات إلى المرحلة القادمة إن أردنا استخدام مرحلة ما لتبديل جزء من خط البناء مثل الاختبارات أو التنبيهات. لهذا الأمر تبريراته في بعض الحالات كأن تَنسخ الشيفرة من مرحلة الاختبار إلى مرحلة البناء كي تضمن بناء الشيفرة المُختبرة.
</p>

<h2>
	التمرينان 12.13 و 12.14
</h2>

<p>
	حاول أن تحل التمرينين التاليين:
</p>

<h3>
	التمرين 12.13: الواجهة الامامية لتطبيق المهام
</h3>

<p>
	لقد وصلنا أخيرًا إلى الواجهة الأمامية، لهذا عليك أن تلقي نظرة على محتويات المجلد "todo-app/todo-frontend" وتقرأ ملف "اقرأني README". ابدأ بتشغيل الواجهة الأمامية خارج الحاوية، وتأكد من تناغم عملها مع الواجهة الخلفية. ضع التطبيق ضمن الحاوية بعد ذلك أنشئ الملف "todo-app/todo-frontend/Dockerfile" ثم استخدم التعليمة <a href="https://docs.docker.com/engine/reference/builder/#env" rel="external nofollow"><code>ENV</code></a> لتمرير متغير البيئة <code>REACT_APP_BACKEND_URL</code> إلى التطبيق وشغّله مع الواجهة الخلفية. ينبغي أن تعمل الواجهة الخلفية حاليًا خارج الحاوية. وانتبه إلى ضرورة ضبط المتغير <code>REACT_APP_BACKEND_URL</code> قبل بناء الواجهة الأمامية وإلا لن يُعرَّف ضمن الشيفرة.
</p>

<h3>
	التمرين 12.14: إجراء الاختبارات أثناء عملية البناء
</h3>

<p>
	من الميزات الهامة للبناء متعدد المراحل، استخدام مرحلة البناء لإجراء الاختبارات على التطبيق <a href="https://docs.docker.com/language/nodejs/run-tests/" rel="external nofollow">testing</a>. فإن أخفقت مرحلة الاختبار ستُخفق عملية البناء بأكملها. لكن تنفيذ الاختبارات جميعها خلال عملية بناء الصورة ليست فكرة جيدة؛ لهذا قد تكون الاختبارات المتعلقة بالحاوية هي الأنسب.
</p>

<p>
	استخلص مكوّن <code>Todo</code> يمثل مهمة واحدة من الشيفرة ، ثم اكتب اختبارًا للمكوّن الجديد وأضف تنفيذ الاختبارات إلى عملية البناء.
</p>

<p>
	نفّذ الاختبار من خلال الأمر <code>CI=true npm test</code> وإلا سيبدأ برنامج create-react-app بمراقبة التغييرات مسببًا توقف خط العمل pipeline.
</p>

<p>
	بإمكانك إضافة مرحلة بناء جديدة لإجراء الاختبار إن أردت ذلك. لكن تذكر في هذه الحالة أن تقرأ آخر فقرة قبل التمرين 12.13 مجددًا.
</p>

<h2>
	التطوير ضمن الحاويات
</h2>

<p>
	لننقل الآن تطبيق المهام بأكمله إلى حاوية. وإليك بعض الأسباب التي قد تدفعنا إلى ذلك:
</p>

<ul>
<li>
		للإبقاء على تماثل بيئة العمل بين التطوير والإنتاج تفاديًا للثغرات التي تظهر فقط في بيئة التشغيل.
	</li>
	<li>
		لتفادي الاختلافات بين المطورين وبيئات عملهم الخاصة والتي تقود إلى صعوبات أثناء تطوير التطبيق.
	</li>
	<li>
		لمساعدة أعضاء الفريق الجدد على البدء بتثبيت بيئة تشغيل الحاوية دون الحاجة إلى أي شيء آخر.
	</li>
</ul>
<p>
	في المقابل، قد نواجه سلوكًا غير معهود عندما لا نشغّل التطبيق كما اعتدنا، ولهذا لا بد من فعل ما يلي على الأقل لنقل التطبيق إلى الحاوية:
</p>

<ul>
<li>
		تشغيل التطبيق في وضع التطوير.
	</li>
	<li>
		الوصول إلى الشيفرة من خلال برنامج <a href="https://academy.hsoub.com/apps/productivity/%D8%A3%D9%81%D8%B6%D9%84-%D8%A7%D9%84%D8%A5%D8%B6%D8%A7%D9%81%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D8%AC%D8%A7%D9%86%D9%8A%D8%A9-%D9%84%D9%84%D9%85%D8%AD%D8%B1%D8%B1-vs-code-r349/" rel="">VSCode</a>.
	</li>
</ul>
<p>
	لنبدأ بالواجهة الأمامية، وطالما أن ملف Dockerfile لمرحلة التطوير سيختلف تمامًا عن ملف Dockerfile لنسخة الإنتاج، سننشئ ملف جديد اسمه "dev.Dockerfile".
</p>

<p>
	لنشغّل create-react-app في وضع التطوير ومن المفترض أن يكون الأمر بسيطًا على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5120_33" style="">
<span class="pln">FROM node</span><span class="pun">:</span><span class="lit">16</span><span class="pln">

WORKDIR </span><span class="pun">/</span><span class="pln">usr</span><span class="pun">/</span><span class="pln">src</span><span class="pun">/</span><span class="pln">app

COPY </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln">
</span><span class="pun">#</span><span class="pln"> </span><span class="pun">طالما</span><span class="pln"> </span><span class="pun">سنعمل</span><span class="pln"> </span><span class="pun">في</span><span class="pln"> </span><span class="pun">وضع</span><span class="pln"> </span><span class="pun">التطوير</span><span class="pln"> npm install </span><span class="pun">إلى</span><span class="pln"> npm ci </span><span class="pun">غيّر</span><span class="pln"> 

RUN npm install
</span><span class="pun">#</span><span class="pln"> npm start </span><span class="pun">إن</span><span class="pln"> </span><span class="pun">أمر</span><span class="pln"> </span><span class="pun">التشغيل</span><span class="pln"> </span><span class="pun">في</span><span class="pln"> </span><span class="pun">وضع</span><span class="pln"> </span><span class="pun">التطوير</span><span class="pln"> </span><span class="pun">هو</span><span class="pln"> 

CMD </span><span class="pun">[</span><span class="str">"npm"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"start"</span><span class="pun">]</span></pre>

<p>
	تُستخدم الراية <code>f-</code> أثناء البناء لتحديد الملف الذي يُستخدم، وإلا سيقع الاختيار على الملف Dockerfile، لهذا يكون أمر بناء الصورة على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5120_35" style="">
<span class="pln">docker build </span><span class="pun">-</span><span class="pln">f </span><span class="pun">./</span><span class="pln">dev</span><span class="pun">.</span><span class="typ">Dockerfile</span><span class="pln"> </span><span class="pun">-</span><span class="pln">t hello</span><span class="pun">-</span><span class="pln">front</span><span class="pun">-</span><span class="pln">dev </span><span class="pun">.</span></pre>

<p>
	سيُخدَّم create-react-app على المنفذ 3000، لهذا يمكنك اختباره بتشغيل الحاوية على هذا المنفذ.
</p>

<p>
	تقتضي المهمة الثانية الوصول إلى الملفات باستخدام VSCode، وهناك على الأقل طريقتان لإنجاز الأمر:
</p>

<ul>
<li>
		باستخدام الموسِّع <a href="https://code.visualstudio.com/docs/remote/containers" rel="external nofollow">Visual Studio Code Remote - Containers</a>.
	</li>
	<li>
		باستخدام الأقراص volumes، وبنفس طريقة تخزين البيانات في قاعدة البيانات.
	</li>
</ul>
<p>
	لنتجاوز المهمة الثانية كوننا سنضطر فيها إلى التعامل مع محررات أخرى، ولنجرب تشغيل الحاوية مع الراية <code>v-</code>، فإذا جرى كل شيء على ما يرام ننقل الإعدادات إلى ملف docker-compose. لاستخدام تلك الراية علينا تزويدها بالمجلد الحالي من خلال تنفيذ الأمر <code>pwd</code> الذي يعطي المسار إلى المجلد الحالي. حاول أن تنفِّذ ذلك من خلال الأمر (echo <code>$(pwd</code> في واجهة سطر الأوامر لديك وبالترتيب التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5120_37" style="">
<span class="pln">$ docker run </span><span class="pun">-</span><span class="pln">p </span><span class="lit">3000</span><span class="pun">:</span><span class="lit">3000</span><span class="pln"> </span><span class="pun">-</span><span class="pln">v </span><span class="str">"$(pwd):/usr/src/app/"</span><span class="pln"> hello</span><span class="pun">-</span><span class="pln">front</span><span class="pun">-</span><span class="pln">dev

  </span><span class="typ">Compiled</span><span class="pln"> successfully</span><span class="pun">!</span><span class="pln">

  </span><span class="typ">You</span><span class="pln"> can now view hello</span><span class="pun">-</span><span class="pln">front in the browser</span><span class="pun">.</span></pre>

<p>
	بإمكاننا الآن تعديل الملف "src/App.js" وستُعرض التغييرات مباشرةً على المتصفح. سننقل تاليًا الإعدادات إلى الملف "docker-compose.yml" الذي ينبغي أن يكون موجودًا في جذر المشروع:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5120_39" style="">
<span class="pln">services</span><span class="pun">:</span><span class="pln">
  app</span><span class="pun">:</span><span class="pln">
    image</span><span class="pun">:</span><span class="pln"> hello</span><span class="pun">-</span><span class="pln">front</span><span class="pun">-</span><span class="pln">dev
    build</span><span class="pun">:</span><span class="pln">
      context</span><span class="pun">:</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">#</span><span class="pln"> </span><span class="pun">يختار</span><span class="pln"> </span><span class="pun">السياق</span><span class="pln"> </span><span class="pun">هذا</span><span class="pln"> </span><span class="pun">المجلد</span><span class="pln"> </span><span class="pun">ليكون</span><span class="pln"> </span><span class="pun">سياق</span><span class="pln"> </span><span class="pun">البناء</span><span class="pln"> 
      dockerfile</span><span class="pun">:</span><span class="pln"> dev</span><span class="pun">.</span><span class="typ">Dockerfile</span><span class="pln"> </span><span class="pun">#</span><span class="pln"> </span><span class="pun">الذي</span><span class="pln"> </span><span class="pun">سيُستخدم</span><span class="pln"> </span><span class="typ">Dockerfile</span><span class="pln"> </span><span class="pun">لاختيار</span><span class="pln"> </span><span class="pun">ملف</span><span class="pln"> 
    volumes</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> </span><span class="pun">./:</span><span class="str">/usr/</span><span class="pln">src</span><span class="pun">/</span><span class="pln">app </span><span class="pun">#</span><span class="pln"> </span><span class="pun">يمكن</span><span class="pln"> </span><span class="pun">أن</span><span class="pln"> </span><span class="pun">يكون</span><span class="pln"> </span><span class="pun">المسار</span><span class="pln"> </span><span class="pun">نسبي,</span><span class="pln"> so </span><span class="pun">./</span><span class="pln"> is enough to say 
                        </span><span class="pun">#</span><span class="pln"> </span><span class="pun">./</span><span class="pln"> </span><span class="pun">لهذا</span><span class="pln"> </span><span class="pun">يكفي</span><span class="pln"> </span><span class="pun">استخدام</span><span class="pln"> 
                        </span><span class="pun">#</span><span class="pln">docker</span><span class="pun">-</span><span class="pln">compose</span><span class="pun">.</span><span class="pln">yml </span><span class="pun">للقول</span><span class="pln"> </span><span class="pun">أنه</span><span class="pln"> </span><span class="pun">نفس</span><span class="pln"> </span><span class="pun">مكان</span><span class="pln"> </span><span class="pun">وجود</span><span class="pln"> </span><span class="pun">الملف</span><span class="pln">

    ports</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> </span><span class="lit">3000</span><span class="pun">:</span><span class="lit">3000</span><span class="pln">
    container_name</span><span class="pun">:</span><span class="pln"> hello</span><span class="pun">-</span><span class="pln">front</span><span class="pun">-</span><span class="pln">dev </span><span class="pun">#</span><span class="pln"> hello</span><span class="pun">-</span><span class="pln">front</span><span class="pun">-</span><span class="pln">dev </span><span class="pun">لتسمية</span><span class="pln"> </span><span class="pun">الحاوية</span><span class="pln"> </span><span class="pun">بالاسم</span><span class="pln"> </span></pre>

<p>
	يمكننا بهذه الإعدادات الآن تشغيل التطبيق في وضع التطوير من خلال الأمر <code>docker-compose up</code>، ولن تحتاج حتى إلى تثبيت Node.
</p>

<p>
	يسبب تثبيت اعتماديات جديدة عدة مشاكل في إعداد بيئة تطوير كهذه، لهذا ستجد أن تثبيت الاعتمادية الجديدة ضمن الحاوية هو أحد الخيارات الجيدة. فبلدلًا من تنفيذ الأمر التالي مثلًا <code>npm install axios</code> ، ثبّت هذه الاعتمادية ضمن الحاوية التي تعمل من خلال الأمر <code>docker exec hello-front-dev npm install axios</code> أو أضفها إلى الملف ثم نفِّذ الأمر <code>docker build</code> من جديد.
</p>

<h2>
	التمرين 12.15: إعداد بيئة تطوير الواجهة الأمامية
</h2>

<p>
	أنشئ الملف واستخدم الأقراص لتمكين تطوير الواجهة الأمامية لتطبيق المهام عندما يعمل ضمن الحاوية.
</p>

<h2>
	التواصل بين الحاويات في شبكة Docker
</h2>

<p>
	تهيئ الأداة Docker شبكةً بين الحاويات وتضيف خادمًا لأسماء النطاقات DNS لربط أي حاويتين بسهولة. دعونا إذًا نضيف خدمة جديدة إلى وسنرى كيف تعمل الشبكة وخادم DNS.
</p>

<p>
	سنستخدم الحزمة التنفيذية <a href="https://www.busybox.net/" rel="external nofollow">Busybox</a> التي تضم مجموعةً من الأدوات التي قد تحتاجها وتُعرف هذه الحزمة باسم "سكين الجيش السويسري الخاصة <a href="https://academy.hsoub.com/devops/linux/%D9%85%D8%A7-%D9%87%D9%88-%D9%86%D8%B8%D8%A7%D9%85-%D8%A7%D9%84%D8%AA%D8%B4%D8%BA%D9%8A%D9%84-%D9%84%D9%8A%D9%86%D9%83%D8%B3%D8%9F-r451/" rel="">بنظام Linux</a> المدمج". لهذا يمكننا بالتأكيد الاستفادة منها.
</p>

<p>
	تساعدنا Busybox في تنقيح إعداداتنا، لهذا إن لم تتمكن من حل التمرين السابق، عليك استخدام Busybox لمعرفة ما يعمل من إعداداتك وما لا يعمل. لنختبر ما قلناه الآن. تتواجد تلك الحاويات ضمن شبكة ويمكنك الربط بينها بسهولة، ويمكن إضافة Busybox إلى الخلطة بتغيير الملف "docker-compose.yml" إلى:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5120_42" style="">
<span class="pln">services</span><span class="pun">:</span><span class="pln">
  app</span><span class="pun">:</span><span class="pln">
    image</span><span class="pun">:</span><span class="pln"> hello</span><span class="pun">-</span><span class="pln">front</span><span class="pun">-</span><span class="pln">dev
    build</span><span class="pun">:</span><span class="pln">
      context</span><span class="pun">:</span><span class="pln"> </span><span class="pun">.</span><span class="pln">
      dockerfile</span><span class="pun">:</span><span class="pln"> dev</span><span class="pun">.</span><span class="typ">Dockerfile</span><span class="pln">
    volumes</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> </span><span class="pun">./:</span><span class="str">/usr/</span><span class="pln">src</span><span class="pun">/</span><span class="pln">app
    ports</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> </span><span class="lit">3000</span><span class="pun">:</span><span class="lit">3000</span><span class="pln">
    container_name</span><span class="pun">:</span><span class="pln"> hello</span><span class="pun">-</span><span class="pln">front</span><span class="pun">-</span><span class="pln">dev
  </span><span class="typ">Debug</span><span class="pun">-</span><span class="pln">helper</span><span class="pun">:</span><span class="pln">
    image</span><span class="pun">:</span><span class="pln"> busybox</span></pre>

<p>
	لن تتضمن حاوية Busybox على أية عمليات تجري ضمنها لذلك يمكننا تنفيذ الأمر <code>exec</code>. عندها سيبدو الخرج الناتج عن تنفيذ التعليمة <code>docker-compose up</code> على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5120_44" style="">
<span class="pln">$ docker</span><span class="pun">-</span><span class="pln">compose up
  </span><span class="typ">Pulling</span><span class="pln"> debug</span><span class="pun">-</span><span class="pln">helper </span><span class="pun">(</span><span class="pln">busybox</span><span class="pun">:)...</span><span class="pln">
  latest</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Pulling</span><span class="pln"> from library</span><span class="pun">/</span><span class="pln">busybox
  </span><span class="lit">8ec32b265e94</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Pull</span><span class="pln"> complete
  </span><span class="typ">Digest</span><span class="pun">:</span><span class="pln"> sha256</span><span class="pun">:</span><span class="pln">b37dd066f59a4961024cf4bed74cae5e68ac26b48807292bd12198afa3ecb778
  </span><span class="typ">Status</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Downloaded</span><span class="pln"> newer image </span><span class="kwd">for</span><span class="pln"> busybox</span><span class="pun">:</span><span class="pln">latest
  </span><span class="typ">Starting</span><span class="pln"> hello</span><span class="pun">-</span><span class="pln">front</span><span class="pun">-</span><span class="pln">dev          </span><span class="pun">...</span><span class="pln"> done
  </span><span class="typ">Creating</span><span class="pln"> react</span><span class="pun">-</span><span class="pln">app_debug</span><span class="pun">-</span><span class="pln">helper_1 </span><span class="pun">...</span><span class="pln"> done
  </span><span class="typ">Attaching</span><span class="pln"> to react</span><span class="pun">-</span><span class="pln">app_debug</span><span class="pun">-</span><span class="pln">helper_1</span><span class="pun">,</span><span class="pln"> hello</span><span class="pun">-</span><span class="pln">front</span><span class="pun">-</span><span class="pln">dev
  react</span><span class="pun">-</span><span class="pln">app_debug</span><span class="pun">-</span><span class="pln">helper_1 exited </span><span class="kwd">with</span><span class="pln"> code </span><span class="lit">0</span><span class="pln">

  hello</span><span class="pun">-</span><span class="pln">front</span><span class="pun">-</span><span class="pln">dev </span><span class="pun">|</span><span class="pln"> 
  hello</span><span class="pun">-</span><span class="pln">front</span><span class="pun">-</span><span class="pln">dev </span><span class="pun">|</span><span class="pln"> </span><span class="pun">&gt;</span><span class="pln"> react</span><span class="pun">-</span><span class="pln">app@0</span><span class="pun">.</span><span class="lit">1.0</span><span class="pln"> start
  hello</span><span class="pun">-</span><span class="pln">front</span><span class="pun">-</span><span class="pln">dev </span><span class="pun">|</span><span class="pln"> </span><span class="pun">&gt;</span><span class="pln"> react</span><span class="pun">-</span><span class="pln">scripts start</span></pre>

<p>
	هذا الخرج متوقع كون الحزمة هي مجموعة أدوات مثل غيرها. لنستخدم الحزمة في إرسال طلب إلى الحاوية "hello-front-dev" لنرى كيف يعمل خادم DNS. بإمكاننا تنفيذ الطلب <a href="https://en.wikipedia.org/wiki/Wget" rel="external nofollow">wget</a> أثناء عمل الحاوية فهو أداةٌ موجودةٌ ضمن Busybox مهمتها إرسال طلب إلى الحاوية hello-front-dev من مساعد التنقيح debug-helper.
</p>

<p>
	يمكننا استخدام الأمر <code>docker-compose run SERVICE COMMAND</code> لتنفيذ خدمة مع أمر محدد، ويتطلب استخدام الأمر wget السابق الراية <code>O-</code> تليها <code>-</code> لنقل الاستجابة إلى مجرى الخرج:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5120_46" style="">
<span class="pln">$ docker</span><span class="pun">-</span><span class="pln">compose run debug</span><span class="pun">-</span><span class="pln">helper wget </span><span class="pun">-</span><span class="pln">O </span><span class="pun">-</span><span class="pln"> http</span><span class="pun">:</span><span class="com">//app:3000</span><span class="pln">

  </span><span class="typ">Creating</span><span class="pln"> react</span><span class="pun">-</span><span class="pln">app_debug</span><span class="pun">-</span><span class="pln">helper_run </span><span class="pun">...</span><span class="pln"> done
  </span><span class="typ">Connecting</span><span class="pln"> to hello</span><span class="pun">-</span><span class="pln">front</span><span class="pun">-</span><span class="pln">dev</span><span class="pun">:</span><span class="lit">3000</span><span class="pln"> </span><span class="pun">(</span><span class="lit">172.26</span><span class="pun">.</span><span class="lit">0.2</span><span class="pun">:</span><span class="lit">3000</span><span class="pun">)</span><span class="pln">
  writing to stdout
  </span><span class="pun">&lt;!</span><span class="pln">DOCTYPE html</span><span class="pun">&gt;</span><span class="pln">
  </span><span class="pun">&lt;</span><span class="pln">html lang</span><span class="pun">=</span><span class="str">"en"</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">head</span><span class="pun">&gt;</span><span class="pln">
      </span><span class="pun">&lt;</span><span class="pln">meta charset</span><span class="pun">=</span><span class="str">"utf-8"</span><span class="pln"> </span><span class="pun">/&gt;</span><span class="pln">
      </span><span class="pun">...</span></pre>

<p>
	يُعد <a href="https://academy.hsoub.com/programming/general/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D8%B9%D9%86%D9%88%D8%A7%D9%86-url-%D9%88%D8%A3%D9%86%D9%88%D8%A7%D8%B9%D9%87-r1435/" rel="">عنوان URL</a> الجزء المهم هنا، فهو يشير إلى أننا اتصلنا بالخدمة "hello-front-dev" والمنفذ 3000. لقد منحنا الحاوية اسمها "hello-front-dev" باستخدام التعليمة <code>container_name</code> في ملف "docker-compose"، أما المنفذ فهو المنفذ المتاح للوصول إلى التطبيق ضمن الحاوية. ولا حاجة لنشر المنفذ كي تتصل به بقية الخدمات الموجودة على نفس الشبكة، فالمنافذ المحددة في الملف "docker-compose" هي للوصول الداخلي وحسب.
</p>

<p>
	دعونا نغيّر رقم المنفذ في الملف "docker-compose.yml" لتوضيح الأمر:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5120_49" style="">
<span class="pln">services</span><span class="pun">:</span><span class="pln">
  app</span><span class="pun">:</span><span class="pln">
    image</span><span class="pun">:</span><span class="pln"> hello</span><span class="pun">-</span><span class="pln">front</span><span class="pun">-</span><span class="pln">dev
    build</span><span class="pun">:</span><span class="pln">
      context</span><span class="pun">:</span><span class="pln"> </span><span class="pun">.</span><span class="pln">
      dockerfile</span><span class="pun">:</span><span class="pln"> dev</span><span class="pun">.</span><span class="typ">Dockerfile</span><span class="pln">
    volumes</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> </span><span class="pun">./:</span><span class="str">/usr/</span><span class="pln">src</span><span class="pun">/</span><span class="pln">app
    ports</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> </span><span class="lit">3210</span><span class="pun">:</span><span class="lit">3000</span><span class="pln">
    container_name</span><span class="pun">:</span><span class="pln"> hello</span><span class="pun">-</span><span class="pln">front</span><span class="pun">-</span><span class="pln">dev
  debug</span><span class="pun">-</span><span class="pln">helper</span><span class="pun">:</span><span class="pln">
    image</span><span class="pun">:</span><span class="pln"> busybox</span></pre>

<p>
	سيُتاح التطبيق على الحاسوب المضيف وعلى العنوان <a href="http://localhost:3210" ipsnoembed="false" rel="external nofollow">http://localhost:3210</a> عند تنفيذ الأمر <code>docker-compose up</code>، لكن التطبيق لا يزال يعمل وفقًا للأمر السابق <code>docker-compose run debug-helper wget -O - http://app:3000</code> طالما أن المنفذ هو 3000 أيضًا ضمن شبكة دوكر.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="105127" href="https://academy.hsoub.com/uploads/monthly_2022_08/01_docker_network.png.75669a724257ea0742fd3a02d9e1dfa6.png" rel=""><img alt="01_docker_network.png" class="ipsImage ipsImage_thumbnailed" data-fileid="105127" data-unique="494a1ispu" src="https://academy.hsoub.com/uploads/monthly_2022_08/01_docker_network.png.75669a724257ea0742fd3a02d9e1dfa6.png"></a>
</p>

<p>
	يطلب الأمر <code>docker-compose run</code> -كما تشرح الصورة السابقة-من مساعد التنقيح أن يرسل طلبًا ضمن شبكة دوكر docker بينما يرسل المتصفح في الجهاز المضيف الطلب من خارج الشبكة.
</p>

<p>
	أما الآن وقد علمت سهولة إيجاد الخدمات في الملف "docker-compose.yml" وليس لدينا أي شيء للتنقيح، سنزيل مساعد التنقيح و نُعيد المنافذ إلى 3000:3000 في الملف "docker-compose.yml".
</p>

<h2>
	التمرين 12.16: تشغيل الواجهة الخلفية لتطبيق المهام ضمن حاوية التطوير
</h2>

<p>
	استخدم الأقراص والمكتبة Nodemon لتمكين عملية تطوير الواجهة الخلفية لتطبيق المهام وهي تعمل ضمن الحاوية. أنشئ الملف "todo-backend/dev.Dockerfile" وعدّل الملف "todo-backend/docker-compose.dev.yml".
</p>

<p>
	عليك إعادة التفكير أيضًا في الاتصالات بين الواجهة الخلفية و قاعدة البيانات MongoDB، أو Redis. ولحسن الحظ يدعم docker-compose استخدام متحولات بيئة يمكن تمريرها إلى التطبيق:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5120_52" style="">
<span class="pln">services</span><span class="pun">:</span><span class="pln">
  server</span><span class="pun">:</span><span class="pln">
    image</span><span class="pun">:</span><span class="pln"> </span><span class="pun">...</span><span class="pln">
    volumes</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> </span><span class="pun">...</span><span class="pln">
    ports</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> </span><span class="pun">...</span><span class="pln">
    environment</span><span class="pun">:</span><span class="pln"> 
      </span><span class="pun">-</span><span class="pln"> REDIS_URL</span><span class="pun">=...</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> MONGO_URL</span><span class="pun">=...</span></pre>

<p>
	وُضعت عناوين URL للخادم المحلي بطريقة خاطئة عمدًا وعليك وضع القيم الصحيحة. وتذكر أن تراقب دائمًا ما يحدث على شاشة الطرفية، فقد ستلمّح رسائل الخطأ إلى مكان المشكلة إن حدث خلل ما.
</p>

<p>
	إليك هذه الصورة التوضيحة التي قد تنفع في توضيح الاتصالات ضمن شبكة docker:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="105128" href="https://academy.hsoub.com/uploads/monthly_2022_08/02_docker_network_connections.png.2b6070613c487f459f5ee344bb59b7c1.png" rel=""><img alt="02_docker_network_connections.png" class="ipsImage ipsImage_thumbnailed" data-fileid="105128" data-unique="dp3ahtrux" src="https://academy.hsoub.com/uploads/monthly_2022_08/02_docker_network_connections.png.2b6070613c487f459f5ee344bb59b7c1.png"></a>
</p>

<h2>
	التواصل بين الحاويات في بيئة أكثر حيوية
</h2>

<p>
	سنضيف تاليًا <a href="https://academy.hsoub.com/devops/servers/web/apache/%D9%83%D9%8A%D9%81-%D8%AA%D8%B3%D8%AA%D8%AE%D8%AF%D9%85-apache-%D9%84%D9%8A%D8%B9%D9%85%D9%84-%D9%88%D8%B3%D9%8A%D8%B7%D9%8B%D8%A7-%D8%B9%D9%83%D8%B3%D9%8A%D9%91%D9%8B%D8%A7-reverse-proxy-%D8%B9%D9%84%D9%89-ubuntu-1604-r339/" rel="">خادم وكيل معكوس</a> reverse proxy إلى الملف " docker-compose.yml". واستنادًا إلى ويكيبيديا:
</p>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p>
		<strong>الخادم الوكيل المعكوس</strong> هو نوع من أنواع الخوادم الوكيلة التي يُستخدم لإحضار الموارد إلى المستخدم نيابة عن العميل من خادم أو أكثر. تُعاد هذه الموارد إلى العميل ويبدو كأن مصدرها الأصلي هو الخادم الوكيل المعكوس نفسه.
	</p>
</blockquote>

<p>
	سيكون الخادم الوكيل المعكوس في حالتنا نقطة دخول مفردة إلى تطبيقنا، أم الهدف النهائي فهو إعداد واجهة React الأمامية و واجهة Express الخلفية معًا خلف <a href="https://academy.hsoub.com/devops/servers/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%AE%D8%A7%D8%AF%D9%85-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-r574/" rel="">الخادم</a> والوكيل المعكوس.
</p>

<p>
	لديك عدة خيارات تساعدك في إنجاز الخادم الوكيل، مثل Traefik و Caddy و Nginx و Apache وقد رُتبت من الأحدث إلى الأقدم ظهورًا، لكن خيارنا سيكون <a href="https://hub.docker.com/_/nginx" rel="external nofollow">Nginx</a>.
</p>

<p>
	لنضع الآن الواجهة الأمامية المتمثلة بالحاوية "hello-frontend" خلف الخادم الوكيل المعكوس. لهذا عليك إنشاء الملف "nginx.conf" في جذر المشروع واتّبع القالب التالي بمثابة نقطة انطلاق. لا بُد من إنجاز بعض التعديلات الثانوية لتشغيل التطبيق:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5120_55" style="">
<span class="pun">#</span><span class="pln"> </span><span class="pun">الأحداث</span><span class="pln"> </span><span class="pun">مطلوبة</span><span class="pln"> </span><span class="pun">لكن</span><span class="pln"> </span><span class="pun">لا</span><span class="pln"> </span><span class="pun">بأس</span><span class="pln"> </span><span class="pun">بالاعتماد</span><span class="pln"> </span><span class="pun">على</span><span class="pln"> </span><span class="pun">الأحداث</span><span class="pln"> </span><span class="pun">الافتراضية</span><span class="pln">
events </span><span class="pun">{</span><span class="pln"> </span><span class="pun">}</span><span class="pln">

</span><span class="pun">#</span><span class="pln"> </span><span class="lit">80</span><span class="pln"> </span><span class="pun">يستمع</span><span class="pln"> </span><span class="pun">إلى</span><span class="pln"> </span><span class="pun">المنفذ</span><span class="pln"> http </span><span class="pun">خادم</span><span class="pln"> 
http </span><span class="pun">{</span><span class="pln">
  server </span><span class="pun">{</span><span class="pln">
    listen </span><span class="lit">80</span><span class="pun">;</span><span class="pln">

    </span><span class="pun">#</span><span class="pln"> </span><span class="pun">(/)</span><span class="pln"> </span><span class="pun">تُعالج</span><span class="pln"> </span><span class="pun">الطلبات</span><span class="pln"> </span><span class="pun">التي</span><span class="pln"> </span><span class="pun">تبدأ</span><span class="pln"> </span><span class="pun">بالمحرف</span><span class="pln"> 
       location </span><span class="pun">/</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="pun">#</span><span class="pln"> </span><span class="pun">نحتاج</span><span class="pln"> </span><span class="pun">الأسطر</span><span class="pln"> </span><span class="pun">الثلاثة</span><span class="pln"> </span><span class="pun">التالية</span><span class="pln"> </span><span class="pun">للتحميل</span><span class="pln"> </span><span class="pun">المباشر</span><span class="pln"> </span><span class="pun">للتغييرات</span><span class="pln">

      proxy_http_version </span><span class="lit">1.1</span><span class="pun">;</span><span class="pln">
      proxy_set_header </span><span class="typ">Upgrade</span><span class="pln"> $http_upgrade</span><span class="pun">;</span><span class="pln">
      proxy_set_header </span><span class="typ">Connection</span><span class="pln"> </span><span class="str">'upgrade'</span><span class="pun">;</span><span class="pln">
      </span><span class="pun">#</span><span class="pln"> http</span><span class="pun">:</span><span class="com">//localhost:3000 تُوجَّه الطلبات مباشرةً إلى العنوان </span><span class="pln">
      proxy_pass http</span><span class="pun">:</span><span class="com">//localhost:3000;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	أنشئ تاليًا خدمة Nginx ضمن الملف "docker-compose.yml"، ثم أضف قرصًا كما ورد في إرشادات الصفحة الرسمية للأداة Docker Hub حيث يكون الجانب الأيمن على الشكل هو: <code>etc/nginx/nginx.conf:ro/:</code>،إذ يدل التصريح <code>ro</code> على أن القرص للقراءة فقط:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5120_57" style="">
<span class="pln">services</span><span class="pun">:</span><span class="pln">
  app</span><span class="pun">:</span><span class="pln">
    </span><span class="pun">#</span><span class="pln"> </span><span class="pun">...</span><span class="pln">
  nginx</span><span class="pun">:</span><span class="pln">
    image</span><span class="pun">:</span><span class="pln"> nginx</span><span class="pun">:</span><span class="lit">1.20</span><span class="pun">.</span><span class="lit">1</span><span class="pln">
    volumes</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> </span><span class="pun">./</span><span class="pln">nginx</span><span class="pun">.</span><span class="pln">conf</span><span class="pun">:</span><span class="str">/etc/</span><span class="pln">nginx</span><span class="pun">/</span><span class="pln">nginx</span><span class="pun">.</span><span class="pln">conf</span><span class="pun">:</span><span class="pln">ro
    ports</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> </span><span class="lit">8080</span><span class="pun">:</span><span class="lit">80</span><span class="pln">
    container_name</span><span class="pun">:</span><span class="pln"> reverse</span><span class="pun">-</span><span class="pln">proxy
    depends_on</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> app </span><span class="pun">#</span><span class="pln"> </span><span class="pun">انتظر</span><span class="pln"> </span><span class="pun">حاوية</span><span class="pln"> </span><span class="pun">الواجهة</span><span class="pln"> </span><span class="pun">الخلفية</span><span class="pln"> </span><span class="pun">حتى</span><span class="pln"> </span><span class="pun">تُقلع</span></pre>

<p>
	يمكنك الآن تنفيذ الأمر ومراقبة نتيجة العمل:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5120_59" style="">
<span class="pln">$ docker container ls
CONTAINER ID   IMAGE             COMMAND                  CREATED         STATUS         PORTS                                       NAMES
a02ae58f3e8d   nginx</span><span class="pun">:</span><span class="lit">1.20</span><span class="pun">.</span><span class="lit">1</span><span class="pln">      </span><span class="str">"/docker-entrypoint.…"</span><span class="pln">   </span><span class="lit">4</span><span class="pln"> minutes ago   </span><span class="typ">Up</span><span class="pln"> </span><span class="lit">4</span><span class="pln"> minutes   </span><span class="lit">0.0</span><span class="pun">.</span><span class="lit">0.0</span><span class="pun">:</span><span class="lit">8080</span><span class="pun">-&gt;</span><span class="lit">80</span><span class="pun">/</span><span class="pln">tcp</span><span class="pun">,</span><span class="pln"> </span><span class="pun">:::</span><span class="lit">8080</span><span class="pun">-&gt;</span><span class="lit">80</span><span class="pun">/</span><span class="pln">tcp       reverse</span><span class="pun">-</span><span class="pln">proxy
</span><span class="lit">5ee0284566b4</span><span class="pln">   hello</span><span class="pun">-</span><span class="pln">front</span><span class="pun">-</span><span class="pln">dev   </span><span class="str">"docker-entrypoint.s…"</span><span class="pln">   </span><span class="lit">4</span><span class="pln"> minutes ago   </span><span class="typ">Up</span><span class="pln"> </span><span class="lit">4</span><span class="pln"> minutes   </span><span class="lit">0.0</span><span class="pun">.</span><span class="lit">0.0</span><span class="pun">:</span><span class="lit">3000</span><span class="pun">-&gt;</span><span class="lit">3000</span><span class="pun">/</span><span class="pln">tcp</span><span class="pun">,</span><span class="pln"> </span><span class="pun">:::</span><span class="lit">3000</span><span class="pun">-&gt;</span><span class="lit">3000</span><span class="pun">/</span><span class="pln">tcp   hello</span><span class="pun">-</span><span class="pln">front</span><span class="pun">-</span><span class="pln">dev</span></pre>

<p>
	عند الانتقال إلى العنوان <a href="http://localhost:8080" ipsnoembed="false" rel="external nofollow">http://localhost:8080</a> ستظهر صفحة الحالة 502 المألوفة، وهذا لأن توجيه الطلبات إلى العنوان <a href="http://localhost:3000" ipsnoembed="false" rel="external nofollow">http://localhost:3000</a> لن يقود إلى أي مكان، لأن حاوية الخادم Nginx لا تحتوي أي تطبيق يعمل على المنفذ 3000. إن مصطلح "خادم محلي" يُستخدم عادة للدلالة على الحاسوب الحالي الذي نلج إليه، بينما يكون الخادم المحلي فريدًا في عالم الحاويات لكل حاوية ويقود إلى الحاوية نفسها.
</p>

<p>
	لنختبر ذلك بالدخول إلى الحاوية Nginx واستخدام الأداة <code>curl</code> لإرسال طلب إلى التطبيق نفسه، وتشابه هذه الأداة من حيث الطريقة التي نستخدمها الأداة wget لكنها لا تحتاج أية رايات:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5120_61" style="">
<span class="pln">$ docker exec </span><span class="pun">-</span><span class="pln">it reverse</span><span class="pun">-</span><span class="pln">proxy bash  

root@374f9e62bfa8</span><span class="pun">:</span><span class="str">/# curl http:/</span><span class="pun">/</span><span class="pln">localhost</span><span class="pun">:</span><span class="lit">80</span><span class="pln">
  </span><span class="pun">&lt;</span><span class="pln">html</span><span class="pun">&gt;</span><span class="pln">
  </span><span class="pun">&lt;</span><span class="pln">head</span><span class="pun">&gt;&lt;</span><span class="pln">title</span><span class="pun">&gt;</span><span class="lit">502</span><span class="pln"> </span><span class="typ">Bad</span><span class="pln"> </span><span class="typ">Gateway</span><span class="pun">&lt;</span><span class="str">/title&gt;&lt;/</span><span class="pln">head</span><span class="pun">&gt;</span><span class="pln">
  </span><span class="pun">...</span></pre>

<p>
	يمكننا الاستفادة من الشبكة التي يهيئها docker-compose عند تنفيذ الأمر <code>docker-compose up</code>، فهي تضيف كل الحاويات الموجودة في الملف "docker-compose.yml" إلى الشبكة. يتأكد خادم DNS من إمكانية إيجاد بقية الحاويات، وتُمنح كل منها اسمان الأول اسم الخدمة والآخر اسم الحاوية.
</p>

<p>
	طالما أننا ضمن الحاوية، سنختبر خادم DNS، لنستخدم إذًا <code>curl</code> لطلب الخدمة التي تُدعى (app) على المنفذ 3000
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5120_63" style="">
<span class="pln">root@374f9e62bfa8</span><span class="pun">:</span><span class="str">/# curl http:/</span><span class="pun">/</span><span class="pln">app</span><span class="pun">:</span><span class="lit">3000</span><span class="pln">
  </span><span class="pun">&lt;!</span><span class="pln">DOCTYPE html</span><span class="pun">&gt;</span><span class="pln">
  </span><span class="pun">&lt;</span><span class="pln">html lang</span><span class="pun">=</span><span class="str">"en"</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">head</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">...</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">meta
      name</span><span class="pun">=</span><span class="str">"description"</span><span class="pln">
      content</span><span class="pun">=</span><span class="str">"Web site created using create-react-app"</span><span class="pln">
    </span><span class="pun">/&gt;</span><span class="pln">
    </span><span class="pun">...</span></pre>

<p>
	هذا كل ما في الأمر. لنبدل الآن عنوان <code>proxy_pass</code> في الملف "nginx.conf" بهذا العنوان (http://app:3000).
</p>

<p>
	إن ظهرت الصفحة 502 مجددًا ، تأكد من بناء create-react-app أولًا، واقرأ الخرج الناتج عن تنفيذ الأمر <code>docker-compose up</code>.
</p>

<p>
	أمر آخر: لقد أضفنا الخيار <a href="https://docs.docker.com/compose/compose-file/compose-file-v3/#depends_on" rel="external nofollow">depends_on</a> إلى الإعدادات لنتأكد أن حاوية "nginx" لن تعمل قبل حاوية الواجهة الأمامية "app":
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5120_65" style="">
<span class="pln">services</span><span class="pun">:</span><span class="pln">
  app</span><span class="pun">:</span><span class="pln">
    </span><span class="pun">#</span><span class="pln"> </span><span class="pun">...</span><span class="pln">
  nginx</span><span class="pun">:</span><span class="pln">
    image</span><span class="pun">:</span><span class="pln"> nginx</span><span class="pun">:</span><span class="lit">1.20</span><span class="pun">.</span><span class="lit">1</span><span class="pln">
    volumes</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> </span><span class="pun">./</span><span class="pln">nginx</span><span class="pun">.</span><span class="pln">conf</span><span class="pun">:</span><span class="str">/etc/</span><span class="pln">nginx</span><span class="pun">/</span><span class="pln">nginx</span><span class="pun">.</span><span class="pln">conf</span><span class="pun">:</span><span class="pln">ro
    ports</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> </span><span class="lit">8080</span><span class="pun">:</span><span class="lit">80</span><span class="pln">
    container_name</span><span class="pun">:</span><span class="pln"> reverse</span><span class="pun">-</span><span class="pln">proxy
    </span><span class="typ">Depends_on</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> app</span></pre>

<p>
	إن لم نفرض تسلسل الإقلاع هذا باستخدام الخيار <code>depends_on</code> فقد نقع في خطر فشل إقلاع لأنه يحاول تحليل أسماء نطاقات DNS التي يُشار إليها في ملف الإعدادات:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5120_67" style="">
<span class="pln">http </span><span class="pun">{</span><span class="pln">
  server </span><span class="pun">{</span><span class="pln">
    listen </span><span class="lit">80</span><span class="pun">;</span><span class="pln">

    location </span><span class="pun">/</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      proxy_http_version </span><span class="lit">1.1</span><span class="pun">;</span><span class="pln">
      proxy_set_header </span><span class="typ">Upgrade</span><span class="pln"> $http_upgrade</span><span class="pun">;</span><span class="pln">
      proxy_set_header </span><span class="typ">Connection</span><span class="pln"> </span><span class="str">'upgrade'</span><span class="pun">;</span><span class="pln">

      proxy_pass http</span><span class="pun">:</span><span class="com">//app:3000;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	تجدر الملاحظة أن الخيار <code>depends_on</code> لا يضمن أن تكون الخدمة في الحاوية جاهزةً للعمل، بل يضمن أن الحاوية قد بدأت العمل وأضيف المُدخل الخاص بها إلى خادم DNS، لكن إذا أردت من خدمة أن تنتظر أخرى حتى تجهز، فعليك الاطلاع على <a href="https://docs.docker.com/compose/startup-order/" rel="external nofollow">حلول أخرى</a>.
</p>

<h2>
	التمرينات 12.17 - 12.19
</h2>

<p>
	حاول إنجاز التمرينات التالية:
</p>

<h3>
	التمرين 12.17: إعداد خادم وكيل معكوس Nginx أمام الواجهة الأمامية لتطبيق المهام
</h3>

<p>
	سنحاول في هذا التمرين وضع خادم Nginx أمام الواجهتين الأمامية والخلفية لتطبيق المهام todo-app. سنبدأ بإنشاء ملف docker-compose جديد "todo-app/docker-compose.dev.yml" وملف تهيئة Nginx يحمل الاسم "todo-app/nginx.conf".
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5120_69" style="">
<span class="pln">todo</span><span class="pun">-</span><span class="pln">app
</span><span class="pun">├──</span><span class="pln"> todo</span><span class="pun">-</span><span class="pln">frontend
</span><span class="pun">├──</span><span class="pln"> todo</span><span class="pun">-</span><span class="pln">backend
</span><span class="pun">├──</span><span class="pln"> nginx</span><span class="pun">.</span><span class="pln">conf
</span><span class="pun">└──</span><span class="pln"> docker</span><span class="pun">-</span><span class="pln">compose</span><span class="pun">.</span><span class="pln">dev</span><span class="pun">.</span><span class="pln">yml</span></pre>

<p>
	أضف الخدمتين <code>nginx</code> و <code>todo-app/todo-frontend/dev.Dockerfile</code> إلى الملف "todo-app/docker-compose.dev.yml".
</p>

<p style="text-align: center;">
	<img alt="03_todo_front_end_proxy.png" class="ipsImage ipsImage_thumbnailed" data-fileid="105129" data-unique="phzhhq4wr" src="https://academy.hsoub.com/uploads/monthly_2022_08/03_todo_front_end_proxy.png.2daf8602a60f12606e933de18e6556ab.png" style=""></p>

<h3>
	التمرين 12.18: إعداد خادم ليكون أمام الواجهة الخلفية لتطبيق المهام
</h3>

<p>
	أضف الخدمة <code>todo-backend</code> إلى الملف "*todo-app/docker-compose.dev.yml" في وضع التطوير، ثم أضف مكانًا جديدًا للملف كي تُخدَّم الطلبات إلى العنوان "api/" عبر الخادم الوكيل إلى الواجهة الخلفية. قد يكون القالب التالي مناسبًا لإنجاز الأمر:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5120_71" style="">
<span class="pln">  server </span><span class="pun">{</span><span class="pln">
    listen </span><span class="lit">80</span><span class="pun">;</span><span class="pln">

    </span><span class="pun">#</span><span class="pln"> </span><span class="pun">(/)</span><span class="pln"> </span><span class="pun">تُعالج</span><span class="pln"> </span><span class="pun">الطلبات</span><span class="pln"> </span><span class="pun">التي</span><span class="pln"> </span><span class="pun">تبدأ</span><span class="pln"> </span><span class="pun">بالمحرف</span><span class="pln"> 
    location </span><span class="pun">/</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="pun">#</span><span class="pln"> </span><span class="pun">نحتاج</span><span class="pln"> </span><span class="pun">الأسطر</span><span class="pln"> </span><span class="pun">الثلاثة</span><span class="pln"> </span><span class="pun">التالية</span><span class="pln"> </span><span class="pun">للتحميل</span><span class="pln"> </span><span class="pun">المباشر</span><span class="pln"> </span><span class="pun">للتغييرات</span><span class="pln">

      proxy_http_version </span><span class="lit">1.1</span><span class="pun">;</span><span class="pln">
      proxy_set_header </span><span class="typ">Upgrade</span><span class="pln"> $http_upgrade</span><span class="pun">;</span><span class="pln">
      proxy_set_header </span><span class="typ">Connection</span><span class="pln"> </span><span class="str">'upgrade'</span><span class="pun">;</span><span class="pln">

       </span><span class="pun">#</span><span class="pln"> http</span><span class="pun">:</span><span class="com">//localhost:3000 تُوجَّه الطلبات مباشرةً إلى العنوان </span><span class="pln">
      proxy_pass http</span><span class="pun">:</span><span class="com">//localhost:3000;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    </span><span class="pun">#</span><span class="pln"> </span><span class="pun">(</span><span class="str">/api/</span><span class="pun">)</span><span class="pln"> </span><span class="pun">تُعالج</span><span class="pln"> </span><span class="pun">الطلبات</span><span class="pln"> </span><span class="pun">التي</span><span class="pln"> </span><span class="pun">تبدأ</span><span class="pln"> </span><span class="pun">بالمسار</span><span class="pln"> 
    location </span><span class="pun">/</span><span class="pln">api</span><span class="pun">/</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="pun">...</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">}</span></pre>

<p>
	للتوجيه <code>proxy_pass</code> ميزةٌ مهمة عندما تُضاف إليه الشرطة المائلة الزائدة "/"، وطالما أننا نستخدم المسار "api/" لتحديد المكان علمًا أن تطبيق الواجهة الخلفية سيجيب فقط على العنوان "/" أو "todos/" فلا بد من إزالة "api/" من الطلب. وبكلمات أخرى، حتى لو أرسل المتصفح الطلب GET إلى العنوان "api/todos/1/" نريد من الخادم Nginx أن ينقل الطلب بالوكالة إلى العنوان "todos/1/". لتنفيذ الأمر لا بد من إضافة شرطة مائلة "/" زائدة إلى العنوان في نهاية التوجيه <code>proxy_pass</code>. انتبه للموضوع جيدًا فقد تقضي ساعات في البحث عن حل لمشاكل سببها إهمال الشرطة الزائدة.
</p>

<p>
	وهذه إحدى <a href="https://serverfault.com/questions/562756/how-to-remove-the-path-with-an-nginx-proxy-pass" rel="external nofollow">المشاكل الشائعة</a>:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="105132" href="https://academy.hsoub.com/uploads/monthly_2022_08/06_nginx_trailing_slash_stackoverflow.png.d191b07c3e6033c35fc2eba7bede5f37.png" rel=""><img alt="06_nginx_trailing_slash_stackoverflow.png" class="ipsImage ipsImage_thumbnailed" data-fileid="105132" data-unique="irkt3arzk" src="https://academy.hsoub.com/uploads/monthly_2022_08/06_nginx_trailing_slash_stackoverflow.png.d191b07c3e6033c35fc2eba7bede5f37.png" style="width: 600px; height: auto;"></a>
</p>

<p>
	إليك هذه الصورة التوضيحة التي قد تنفع في توضيح الأمر عندما تواجهك المشاكل:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="105130" href="https://academy.hsoub.com/uploads/monthly_2022_08/04_todo_back_end_proxy.png.0686f9ee0393759b39dd738231821356.png" rel=""><img alt="04_todo_back_end_proxy.png" class="ipsImage ipsImage_thumbnailed" data-fileid="105130" data-unique="h9kdf5xph" src="https://academy.hsoub.com/uploads/monthly_2022_08/04_todo_back_end_proxy.png.0686f9ee0393759b39dd738231821356.png"></a>
</p>

<h3>
	التمرين 12.19: ربط الخدمتين todo-frontend و todo-backend
</h3>

<p>
	سلّم في هذا التمرين بيئة التطوير بأكملها بما في ذلك تطبيقي Express و React وملفات Dockerfiles والملف "docker-compose.yml".
</p>

<p>
	تأكد بدايةً من عمل الواجهة الأمامية مع الواجهة الخلفية، وسيتطلب ذلك تغييرات في متغير البيئة <code>REACT_APP_BACKEND_URL</code>. وإذا كانت الواجهة جاهزةً للعمل خلال التمرين السابق يمكنك تجاوز هذه الخطوة.
</p>

<p>
	تأكد من أن بيئة التطوير تعمل الآن بكامل طاقتها، أي:
</p>

<ul>
<li>
		أن تعمل جميع ميزات تطبيق المهام.
	</li>
	<li>
		عندما تغيّر الشيفرة المصدرية لا بُد أن تظهر نتيجة التغيرات مباشرةً في حال كان التغيير في الواجهة الأمامية وبإعادة تحميل التطبيق إن كان التغيير في الواجهة الخلفية.
	</li>
</ul>
<h2>
	أدوات لمرحلة الإنتاج
</h2>

<p>
	التعامل مع الحاويات ممتعٌ في مرحلة التطوير، لكن أفضل حالات الاستخدام ستكون في مرحلة الإنتاج، إذ توجد أدوات أكثر قدرة من docker-compose في تشغيل الحاويات في مرحلة الإنتاج.
</p>

<p>
	تسمح لنا أداة تنسيق الحاويات <a href="https://academy.hsoub.com/devops/cloud-computing/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-kubernetes-r467/" rel="">Kubernetes</a> مثلًا بإدارة الحاويات ضمن مستوى جديد بالكامل، وتخفي تلك الأدوات عمومًا التجهيزات الفيزيائية المستخدمة مما يجعل المطورين أقل انشغالًا بأمور البنية التحتية.
</p>

<p>
	إن كنت ترغب في الاطلاع أكثر على الحاويات باستخدام Docker فعليك بالمنهاج <a href="https://devopswithdocker.com/" rel="external nofollow">DevOps with Docker</a>، وكذلك المنهاج <a href="https://devopswithkubernetes.com/" rel="external nofollow">DevOps with Kubernetes</a> لتتعلم تنسيق الحاويات باستخدام Kubernetes.
</p>

<h2>
	التمرينات 12.20-12.22
</h2>

<p>
	حاول إنجاز التمرينات التالية:
</p>

<h3>
	التمرين 12.20
</h3>

<p>
	أنشئ نسخة إنتاج من الملف "todo-app/docker-compose.yml" تضم كل الخدمات: Nginx و todo-backend و todo-frontend و MongoDB و Redis. استخدم الملف "Dockerfiles" بدلًا من "dev.Dockerfiles" وتأكد من تشغيل التطبيق في وضع الإنتاج.
</p>

<p>
	استخدم الهيكلية التالية في هذا التمرين:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5120_75" style="">
<span class="pln">todo</span><span class="pun">-</span><span class="pln">app
</span><span class="pun">├──</span><span class="pln"> todo</span><span class="pun">-</span><span class="pln">frontend
</span><span class="pun">├──</span><span class="pln"> todo</span><span class="pun">-</span><span class="pln">backend
</span><span class="pun">├──</span><span class="pln"> nginx</span><span class="pun">.</span><span class="pln">conf
</span><span class="pun">├──</span><span class="pln"> docker</span><span class="pun">-</span><span class="pln">compose</span><span class="pun">.</span><span class="pln">dev</span><span class="pun">.</span><span class="pln">yml
</span><span class="pun">└──</span><span class="pln"> docker</span><span class="pun">-</span><span class="pln">compose</span><span class="pun">.</span><span class="pln">yml</span></pre>

<h3>
	التمرين 12.21
</h3>

<p>
	أنشئ بيئة تطوير تعتمد على الحاويات مشابهة لما فعلنا، وذلك لأحد التطبيقات التي مرّت معك خلال منهاجنا أو التي أنشاتها في أوقات فراغك. اجعل هيكلية تطبيقك ضمن مستودع التسليم على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5120_77" style="">
<span class="pun">└──</span><span class="pln"> my</span><span class="pun">-</span><span class="pln">app
    </span><span class="pun">├──</span><span class="pln"> frontend
    </span><span class="pun">|</span><span class="pln">    </span><span class="pun">└──</span><span class="pln"> dev</span><span class="pun">.</span><span class="typ">Dockerfile</span><span class="pln">
    </span><span class="pun">├──</span><span class="pln"> backend
    </span><span class="pun">|</span><span class="pln">    </span><span class="pun">└──</span><span class="pln"> dev</span><span class="pun">.</span><span class="typ">Dockerfile</span><span class="pln">
    </span><span class="pun">└──</span><span class="pln"> docker</span><span class="pun">-</span><span class="pln">compose</span><span class="pun">.</span><span class="pln">dev</span><span class="pun">.</span><span class="pln">yml</span></pre>

<h3>
	التمرين 12.22
</h3>

<p>
	أنهِ القسم بإعداد نسخة إنتاج تعتمد على الحاويات خاصة بالتطبيق الذي اخترته. جعل هيكلية تطبيقك ضمن مستودع التسليم على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5120_79" style="">
<span class="pun">└──</span><span class="pln"> my</span><span class="pun">-</span><span class="pln">app
    </span><span class="pun">├──</span><span class="pln"> frontend
    </span><span class="pun">|</span><span class="pln">    </span><span class="pun">├──</span><span class="pln"> dev</span><span class="pun">.</span><span class="typ">Dockerfile</span><span class="pln">
    </span><span class="pun">|</span><span class="pln">    </span><span class="pun">└──</span><span class="pln"> </span><span class="typ">Dockerfile</span><span class="pln">
    </span><span class="pun">├──</span><span class="pln"> backend
    </span><span class="pun">|</span><span class="pln">    </span><span class="pun">└──</span><span class="pln"> dev</span><span class="pun">.</span><span class="typ">Dockerfile</span><span class="pln">
    </span><span class="pun">|</span><span class="pln">    </span><span class="pun">└──</span><span class="pln"> </span><span class="typ">Dockerfile</span><span class="pln">
    </span><span class="pun">├──</span><span class="pln"> docker</span><span class="pun">-</span><span class="pln">compose</span><span class="pun">.</span><span class="pln">dev</span><span class="pun">.</span><span class="pln">yml
    </span><span class="pun">└──</span><span class="pln"> docker</span><span class="pun">-</span><span class="pln">compose</span><span class="pun">.</span><span class="pln">yml</span></pre>

<p>
	ترجمة -وبتصرف- للفصل <a href="https://fullstackopen.com/en/part13/basics_of_orchestration" rel="external nofollow">basics of orchestration</a> من سلسلة <a href="https://fullstackopen.com/en/" rel="external nofollow">Deep Dive Into Modern Web Development</a>
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		المقال السابق: <a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D8%A8%D9%86%D8%A7%D8%A1-%D8%A7%D9%84%D8%B5%D9%88%D8%B1-%D9%88%D8%AA%D9%87%D9%8A%D8%A6%D8%A9-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%84%D9%84%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D8%A8%D9%86%D9%8A%D8%A9-%D8%B6%D9%85%D9%86-%D8%A7%D9%84%D8%AD%D8%A7%D9%88%D9%8A%D8%A7%D8%AA-r642/" rel="">بناء الصور وتهيئة بيئة العمل للتطبيقات المبنية ضمن الحاويات</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D8%AD%D8%A7%D9%88%D9%8A%D8%A7%D8%AA-r641/" rel="">مدخل إلى الحاويات</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D8%AD%D8%A7%D9%88%D9%8A%D8%A7%D8%AA-r641/" rel="">أبرز المفاهيم التي يجب عليك الإلمام بها عن الحاويات</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">643</guid><pubDate>Fri, 30 Sep 2022 17:00:00 +0000</pubDate></item><item><title>&#x628;&#x646;&#x627;&#x621; &#x627;&#x644;&#x635;&#x648;&#x631; &#x648;&#x62A;&#x647;&#x64A;&#x626;&#x629; &#x628;&#x64A;&#x626;&#x629; &#x627;&#x644;&#x639;&#x645;&#x644; &#x644;&#x644;&#x62A;&#x637;&#x628;&#x64A;&#x642;&#x627;&#x62A; &#x627;&#x644;&#x645;&#x628;&#x646;&#x64A;&#x629; &#x636;&#x645;&#x646; &#x627;&#x644;&#x62D;&#x627;&#x648;&#x64A;&#x627;&#x62A;</title><link>https://academy.hsoub.com/devops/cloud-computing/docker/%D8%A8%D9%86%D8%A7%D8%A1-%D8%A7%D9%84%D8%B5%D9%88%D8%B1-%D9%88%D8%AA%D9%87%D9%8A%D8%A6%D8%A9-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%84%D9%84%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D8%A8%D9%86%D9%8A%D8%A9-%D8%B6%D9%85%D9%86-%D8%A7%D9%84%D8%AD%D8%A7%D9%88%D9%8A%D8%A7%D8%AA-r642/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_08/62fb6cdcefec2_--------.png.27b4f9ee8470cfaebbe3093b70ac15ec.png" /></p>

<p>
	استخدمنا في جزئية سابقة من هذه السلسلة صورتين مختلفتين هما "ubuntu" و "node" ونفّذنا بعض الأعمال يدويًا لتشغيل تطبيق "Hello, World". ستساعدنا الأدوات والأوامر التي تعلمناها سابقًا في هذه الجزئية من السلسلة، إذ نتعلم فيه بناء الصور وتهيئة بيئة عمل التطبيق. سنبدأ ببناء واجهة خلفية نمطية باستخدام Express/Node.js ثم نبني عليها مستخدمين خدمات أخرى مثل قواعد بيانات MongoDB.
</p>

<h2>
	ملفات Dockerfile
</h2>

<p>
	بإمكاننا إنشاء صورة جديدة تتضمن التطبيق "!Hello, World" بدلًا من تعديل الحاوية بنسخ ملفات جديدة إليها، وتساعدنا في ذلك أداة تُدعى Dockerfile، وهي ملفٌ نصي بسيط يحتوي كل التعليمات الخاصة بإنشاء صورة. سنبدأ إذًا بإنشاء مثال عن Dockerfile من تطبيق "!Hello, World".
</p>

<p>
	أنشئ مجلدًا جديدًا على جهازك ثم أنشئ ضمنه الملف "Dockerfile " إن لم تكن قد فعلت ذلك مسبقًا. ولنضع كذلك الملف "index.js" الذي يضم الشيفرة <code>('!console.log('Hello, World</code> إلى جواره. ستبدو هيكلية المجلد على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5689_8" style="">
<span class="pun">├──</span><span class="pln"> index</span><span class="pun">.</span><span class="pln">js
</span><span class="pun">└──</span><span class="pln"> </span><span class="typ">Dockerfile</span></pre>

<p>
	سنخبر الصورة من خلال "Dockerfile" بثلاثة أمور:
</p>

<ul>
<li>
		استخدم node:16 أساسًا للصورة.
	</li>
	<li>
		ضع الملف "index.js" ضمن الصورة، كي لا نُضطر إلى نسخه يديويًا إلى <a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D8%AD%D8%A7%D9%88%D9%8A%D8%A7%D8%AA-r641/" rel="">الحاوية</a>.
	</li>
	<li>
		استخدم node لتنفيذ شيفرة الملف "index.js" عندما نشغّل الحاوية من الصورة.
	</li>
</ul>
<p>
	توضع هذه النقاط الثلاث ضمن ملف "Dockerfile" وأفضل مكان لإنشاء هذا الملف هو جذر المشروع، وسيبدو هذا الملف على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5689_11" style="">
<span class="pln">FROM node</span><span class="pun">:</span><span class="lit">16</span><span class="pln">

WORKDIR </span><span class="pun">/</span><span class="pln">usr</span><span class="pun">/</span><span class="pln">src</span><span class="pun">/</span><span class="pln">app

COPY </span><span class="pun">./</span><span class="pln">index</span><span class="pun">.</span><span class="pln">js </span><span class="pun">./</span><span class="pln">index</span><span class="pun">.</span><span class="pln">js

CMD node index</span><span class="pun">.</span><span class="pln">js</span></pre>

<ul>
<li>
		<code>FROM</code>: تخبر هذه التعليمة برنامج دوكر Docker أنّ أساس الصورة هو <code>node:16</code>.
	</li>
	<li>
		<code>COPY</code>: تنسخ هذه التعليمة الملف <code>index.js</code> من الجهاز المضيف إلى ملف بنفس الاسم ضمن الصورة.
	</li>
	<li>
		<code>CMD</code>: تخبر هذه التعليمة البرنامج ما يجب أن يحدث عند تنفيذ الأمر <code>docker run</code>. وهذه التعليمة هي تعليمة تنفيذ افتراضية يمكن استبدالها بالمعامل الذي يُعطي بعد اسم الصورة. اكتب الأمر <code>docker run --help</code> إن نسيت.
	</li>
	<li>
		<code>WORKDIR</code>: وضعت هذه التعليمة للتأكد من أننا لن نفعل شيئًا يتداخل مع محتوى الحاوية. إذ سيضمن أنّ كل التعليمات التي ستليه ستكون ضمن المجلد "usr/src/app/" الذي يُعد مجلد العمل في هذه الحالة. فإن لم يكن هذا المجلد موجودًا في الصورة، سيُنشأ تدريجيًا.
	</li>
</ul>
<p>
	لم نحدد مجلد عمل <code>WORKDIR</code>، فقد نجازف بتغيير ملفات هامة بطريق الخطأ. لو تحققت من الجذر <code>/</code> للصورة بتنفيذ الأمر <code>docker run node:16 ls</code> ستجد العديد من المجلدات والملفات في هذه الصورة.
</p>

<p>
	نستطيع الآن استخدام الأمر <code>docker build</code> لبناء الصورة بناءً على ملف Dockerfile، لكننا سنضيف الراية <code>t-</code> إلى هذا الأمر كي تساعدنا في إعادة تسمية الصورة:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5689_13" style="">
<span class="pln">$ docker build </span><span class="pun">-</span><span class="pln">t fs</span><span class="pun">-</span><span class="pln">hello</span><span class="pun">-</span><span class="pln">world </span><span class="pun">.</span><span class="pln"> 
</span><span class="pun">[+]</span><span class="pln"> </span><span class="typ">Building</span><span class="pln"> </span><span class="lit">3.9s</span><span class="pln"> </span><span class="pun">(</span><span class="lit">8</span><span class="pun">/</span><span class="lit">8</span><span class="pun">)</span><span class="pln"> FINISHED
</span><span class="pun">...</span></pre>

<p>
	ستكون نتيجة تنفيذ الأمر هي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5689_15" style="">
<span class="str">"docker please build with tag fs-hello-world the Dockerfile in this directory"</span><span class="pln"> </span></pre>

<p>
	والتي تشير إلى بناء صورة بالاسم "fs-hello-world" بالاعتماد على ملف Dockerfile الموجود في المجلد. يمكنك الإشارة إلى أي ملف Dockerfile لكن في حالتنا البسيطة يكفي وضع <code>.</code> للإشارة إلى هذا الملف ضمن المجلد لهذا انتهى الأمر بالنقطة.
</p>

<p>
	يمكنك تشغيل الحاوية الآن باستخدام الأمر <code>docker run fs-hello-world</code> ويمكن نقل الصورة أو تنزيلها أو حذفها فهي في طبيعتها ملفات. إذ يمكنك تشكيل قائمة بالصور التي يضمها حاسوبك باستخدام الأمر <code>docker image ls</code> أو حذف الصورة ‍‍<code>docker image rm</code>. اطلع على بقية الأوامر المتاحة بتنفيذ أمر المساعدة <code>docker image --help</code>.
</p>

<h2>
	صور أكثر فائدة
</h2>

<p>
	ينبغي أن يكون نقل خادم Express إلى حاوية ببساطة نقل تطبيق "!Hello, World"، إذ يكمن الاختلاف الوحيد بين الحالتين في وجود ملفات أكثر في حالة الخادم، لكن ستغدو الأمور أبسط بوجود التعليمة <code>COPY</code>.
</p>

<p>
	لنحذف الآن الملف "index.js" وننشئ خادم Express باستخدام <a href="https://expressjs.com/en/starter/generator.html" rel="external nofollow">express-generator</a> الذي يساعدنا على بناء هيكلية بسيطة للتطبيق.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5689_17" style="">
<span class="pln">$ npx express</span><span class="pun">-</span><span class="pln">generator
  </span><span class="pun">...</span><span class="pln">

  install dependencies</span><span class="pun">:</span><span class="pln">
    $ npm install

  run the app</span><span class="pun">:</span><span class="pln">
    $ DEBUG</span><span class="pun">=</span><span class="pln">playground</span><span class="pun">:*</span><span class="pln"> npm start</span></pre>

<p>
	لنشغل التطبيق الآن كي نرى ما فعلنا، وانتبه أن أمر التشغيل قد يختلف في جهازك، فالمجلد في المثال السابق يُدعى "playground".
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5689_19" style="">
<span class="pln">$ npm install
$ DEBUG</span><span class="pun">=</span><span class="pln">playground</span><span class="pun">:*</span><span class="pln"> npm start
  playground</span><span class="pun">:</span><span class="pln">server </span><span class="typ">Listening</span><span class="pln"> on port </span><span class="lit">3000</span><span class="pln"> </span><span class="pun">+</span><span class="lit">0ms</span></pre>

<p>
	يمكنك الانتقال الآن إلى العنوان "http://localhost:3000" حيث يعمل التطبيق.
</p>

<p>
	إن ضم الملفات في حاويات هي عملية سهلة نوعًا ما بناءً على ما واجهناه حتى الآن:
</p>

<ul>
<li>
		استخدم الأساس node.
	</li>
	<li>
		اضبط مجلد العمل كي لا يتداخل عملك مع بقية محتويات الصورة.
	</li>
	<li>
		انسخ كل ملفات المجلد إلى الصورة.
	</li>
	<li>
		ابدأ بتنفيذ الأمر <code>DEBUG=playground:* npm start</code> بعد الأمر <code>CMD</code>.
	</li>
</ul>
<p>
	لنضع ملف Dockerfile التالي في جذر المشروع:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5689_21" style="">
<span class="pln">FROM node</span><span class="pun">:</span><span class="lit">16</span><span class="pln">

WORKDIR </span><span class="pun">/</span><span class="pln">usr</span><span class="pun">/</span><span class="pln">src</span><span class="pun">/</span><span class="pln">app

COPY </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln">

CMD DEBUG</span><span class="pun">=</span><span class="pln">playground</span><span class="pun">:*</span><span class="pln"> npm start</span></pre>

<p>
	سنبني الآن الصورة انطلاقًا من ملف باستخدام الأمر:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5689_23" style="">
<span class="pln">docker build </span><span class="pun">-</span><span class="pln">t express</span><span class="pun">-</span><span class="pln">server </span><span class="pun">.</span></pre>

<p>
	وسنشغلها باستخدام الأمر:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5689_25" style="">
<span class="pln"> docker run </span><span class="pun">-</span><span class="pln">p </span><span class="lit">3123</span><span class="pun">:</span><span class="lit">3000</span><span class="pln"> express</span><span class="pun">-</span><span class="pln">server</span></pre>

<p>
	تبلّغ الراية <code>p-</code> دوكر بضرورة فتح منفذ الجهاز المضيف وتوجيهه إلى منفذ للحاوية ولهذا الأمر الصيغة التالية: <code>p host-port:application-port-</code>
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5689_28" style="">
<span class="pln">$ docker run </span><span class="pun">-</span><span class="pln">p </span><span class="lit">3123</span><span class="pun">:</span><span class="lit">3000</span><span class="pln"> express</span><span class="pun">-</span><span class="pln">server

</span><span class="pun">&gt;</span><span class="pln"> playground@0</span><span class="pun">.</span><span class="lit">0.0</span><span class="pln"> start
</span><span class="pun">&gt;</span><span class="pln"> node </span><span class="pun">./</span><span class="pln">bin</span><span class="pun">/</span><span class="pln">www

</span><span class="typ">Tue</span><span class="pun">,</span><span class="pln"> </span><span class="lit">29</span><span class="pln"> </span><span class="typ">Jun</span><span class="pln"> </span><span class="lit">2021</span><span class="pln"> </span><span class="lit">10</span><span class="pun">:</span><span class="lit">55</span><span class="pun">:</span><span class="lit">10</span><span class="pln"> GMT playground</span><span class="pun">:</span><span class="pln">server </span><span class="typ">Listening</span><span class="pln"> on port </span><span class="lit">3000</span></pre>

<p>
	إن لم يفلح الأمر، تجاوز الفقرة التالية، فهناك تفسير لعدم نجاح الأمر حتى لو اتبعت الخطوات السابقة تمامًا.
</p>

<p>
	سيبدأ التطبيق عمله الآن، لهذا سنختبره بإرسال الطلب GET إلى العنوان "/http://localhost:3123". بالنسبة لإيقاف الخادم فهو أمر عصيبٌ حاليًا، لهذا افتح نافذة أخرى للطرفية ونفذ الأمر <code>docker kill</code> لإيقاف التطبيق؛ إذ يرسل هذا الأمر الإشارة SIGKILL إلى التطبيق ويجبره على الإنهاء. ستحتاج إلى اسم أو معرّف الحاوية ID مثل وسيط لتنفيذ الأمر السابق. وتجدر الإشارة أنه يكفي استخدام بداية المعرّف id عند تمريره وسيطًا، إذ سيعرف دوكر مباشرةً الحاوية المقصودة.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5689_30" style="">
<span class="pln">$ docker container ls
  CONTAINER ID   IMAGE            COMMAND                  CREATED         STATUS         PORTS                                       NAMES
  </span><span class="lit">48096ca3ffec</span><span class="pln">   express</span><span class="pun">-</span><span class="pln">server   </span><span class="str">"docker-entrypoint.s…"</span><span class="pln">   </span><span class="lit">9</span><span class="pln"> seconds ago   </span><span class="typ">Up</span><span class="pln"> </span><span class="lit">6</span><span class="pln"> seconds   </span><span class="lit">0.0</span><span class="pun">.</span><span class="lit">0.0</span><span class="pun">:</span><span class="lit">3123</span><span class="pun">-&gt;</span><span class="lit">3000</span><span class="pun">/</span><span class="pln">tcp</span><span class="pun">,</span><span class="pln"> </span><span class="pun">:::</span><span class="lit">3123</span><span class="pun">-&gt;</span><span class="lit">3000</span><span class="pun">/</span><span class="pln">tcp   infallible_booth

$ docker kill </span><span class="lit">48</span><span class="pln">
  </span><span class="lit">48</span></pre>

<p>
	لنعمل من الآن وصاعدًا على نفس المنفذ في كلا الجانبين <code>p-</code>. وهكذا لن تضطر إلى تذكُّر ما المنفذ الذي عليك اختياره.
</p>

<h3>
	إصلاح المشاكل المحتملة الناتجة عن عملية النسخ واللصق
</h3>

<p>
	لا بُد من تغيير بعض الخطوات لإنشاء ملف Dockerfile متقدم، وقد لا يعمل المثال الذي أوردناه سابقًا على الإطلاق، لأننا أهملنا خطوة هامة.
</p>

<p>
	عندما تنفِّذ الأمر <code>npm install</code> على حاسوبك، فقد يُثبّت مدير حزم Node بعض الاعتمادات التي تتعلق بنظام التشغيل أثناء تقدم التثبيت. وقد ننقل صدفةً أجزاءً غير وظيفية إلى الصورة عند استخدام التعليمة <code>COPY</code>، ويحدث ذلك بسهولة إن نسخنا المجلد "node_modules" إلى الصورة.
</p>

<p>
	من المهم جدًا إبقاء تلك النقاط في ذاكرتنا عند بناء الصورة، فمن الأفضل أن ننفذ معظم الأعمال مثل <code>npm install</code> أثناء عملية البناء ضمن الحاوية بدلًا من تنفيذها قبل البناء؛ إذ أن القاعدة الجوهرية هنا هي نسخ الملفات التي ستدفعها إلى غيت هب GitHub فقط، ولا ينبغي نسخ الاعتماديات ومتطلبات البناء كونها أشياء يمكن تثبيتها أثناء بناء الصورة.
</p>

<p>
	يمكنك استخدام الملف "dockerignore." لحل المشكلة، وهو ملفٌ شبيه بملف التجاهل "gitignore." لمنع نسخ الملفات غير المطلوبة إلى الصورة. يُوضع هذا الملف إلى جوار الملف Dockerfile، وإليك مثالًا عن محتوياته:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5689_34" style="">
<span class="pun">.</span><span class="pln">dockerignore
</span><span class="pun">.</span><span class="pln">gitignore
node_modules
</span><span class="typ">Dockerfile</span></pre>

<p>
	لكننا سنحتاج إضافةً إلى ملف "dockerignore." في حالتنا إلى تثبيت الاعتماديات خلال خطوة البناء، لهذا سيتغير ملف Dockerfile إلى الشكل:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5689_36" style="">
<span class="pln">FROM node</span><span class="pun">:</span><span class="lit">16</span><span class="pln">

WORKDIR </span><span class="pun">/</span><span class="pln">usr</span><span class="pun">/</span><span class="pln">src</span><span class="pun">/</span><span class="pln">app

COPY </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln">

RUN npm install
CMD DEBUG</span><span class="pun">=</span><span class="pln">playground</span><span class="pun">:*</span><span class="pln"> npm start</span></pre>

<p>
	قد يكون تنفيذ الأمر <code>npm install</code> خطرًا، لهذا يزوّدنا <a href="https://academy.hsoub.com/programming/javascript/%D9%81%D9%8A%D8%AF%D9%8A%D9%88-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%85%D8%AF%D9%8A%D8%B1-%D8%A7%D9%84%D8%AD%D8%B2%D9%85-npm-r1225/" rel="">npm</a> بأداة أفضل لتثبيت الاعتماديات وهو الأمر <code>ci</code>.
</p>

<p>
	تُلخّص الاختلافات بين <code>ci</code> و <code>install</code> على النحو التالي :
</p>

<ul>
<li>
		قد يُحدّث <code>install</code> الملف "package-lock.json".
	</li>
	<li>
		قد يُثبِّت <code>install</code> نسخةً مختلفةً من الاعتمادية إن ظهرت المحارف "^" أو "~" في نسخة الاعتمادية.
	</li>
	<li>
		سيحذف <code>ci</code> المجلد "node_modules" قبل تثبيت أي شيء.
	</li>
	<li>
		سيتبع <code>ci</code> الملف "package-lock.json" ولا يبدّل أي ملف.
	</li>
</ul>
<p>
	باختصار: يقدم <code>ci</code> نسخًا يمكن الاعتماد عليها، بينما يُستخدم <code>install</code> عند تثبيت اعتماديات جديدة.
</p>

<p>
	طالما أننا لن نثبِّت أي شيء جديد في خطوة البناء، ولا نريد تغييرات فجائية في النسخ، سنستخدم الأمر <code>ci</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5689_38" style="">
<span class="pln">FROM node</span><span class="pun">:</span><span class="lit">16</span><span class="pln">

WORKDIR </span><span class="pun">/</span><span class="pln">usr</span><span class="pun">/</span><span class="pln">src</span><span class="pun">/</span><span class="pln">app

COPY </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln">

RUN npm ci
CMD DEBUG</span><span class="pun">=</span><span class="pln">playground</span><span class="pun">:*</span><span class="pln"> npm start</span></pre>

<p>
	كما يمكننا تحسين الحالة أكثر باستخدام الأمر <code>npm ci --only=production</code> كي لا نهدر الوقت في تثبيت الاعتماديات.
</p>

<p>
	سيحذف <code>ci</code> المجلد "node_modules" كما أشرنا قبل قليل، وبالتالي لن نضطر إلى إنشاء الملف "dockerignore.". مع ذلك، يُعد هذا الملف أداةً رائعةً عندما تريد تحسين عملية البناء، وسنتحدث باختصار عن هذا الموضوع لاحقًا.
</p>

<p>
	ينبغي أن يعمل الملف من جديد، لهذا حاول تنفيذ الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5689_44" style="">
<span class="pln">docker build </span><span class="pun">-</span><span class="pln">t express</span><span class="pun">-</span><span class="pln">server </span><span class="pun">.</span><span class="pln"> </span><span class="pun">&amp;&amp;</span><span class="pln"> docker run </span><span class="pun">-</span><span class="pln">p </span><span class="lit">3000</span><span class="pun">:</span><span class="lit">3000</span><span class="pln"> express</span><span class="pun">-</span><span class="pln">server</span></pre>

<p>
	لاحظ كيف وصلنا هنا أمري <a href="https://wiki.hsoub.com/Bash" rel="external">باش bash</a> باستخدام <code>&amp;&amp;</code>، وسنحصل تقريبًا على نفس النتيجة إذا نفذنا كلا الأمرين كلًّا على حدة؛ لكن عندما تربط أمرين باستخدام <code>&amp;&amp;</code>، فلن يُنفَّذ الأمر الآخر إذا فشل تنفيذ أحدهما.
</p>

<p>
	ضبطنا سابقًا متغير البيئة <code>:DEBUG=playground</code> من خلال الأمر <code>CMD</code> لتشغيل npm، كما يمكننا أيضًا استخدام التعليمة <code>ENV</code> في ملف Dockerfiles لضبط متغيرات البيئة، فلنفعل ذلك:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5689_48" style="">
<span class="pln">FROM node</span><span class="pun">:</span><span class="lit">16</span><span class="pln">

WORKDIR </span><span class="pun">/</span><span class="pln">usr</span><span class="pun">/</span><span class="pln">src</span><span class="pun">/</span><span class="pln">app

COPY </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln">

RUN npm ci 

ENV DEBUG</span><span class="pun">=</span><span class="pln">playground</span><span class="pun">:*</span><span class="pln">
CMD npm start</span></pre>

<h3>
	أفضل الممارسات المتعلقة باستخدام Dockerfiles
</h3>

<p>
	عليك اتباع القاعدتين الجوهريّتين التاليتين عند إنشاء الصور:
</p>

<ul>
<li>
		حاول أن تبني صورةً آمنة قدر المستطاع.
	</li>
	<li>
		حاول أن تُنشئ صورةً صغيرة قدر الإمكان.
	</li>
</ul>
<p>
	تُعد الصور الأصغر أكثر أمانًا لأن مجال الهجوم عليها محدود، كما يمكن نقلها بسرعة أكبر ضمن أنابيب النشر.
</p>

<p>
	وأخيرًا لا بُد من إصلاح آخر نقطة أهملناها وهي تشغيل التطبيق مثل جذر بدلًا من مستخدم بصلاحيات منخفضة:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5689_50" style="">
<span class="pln">FROM node</span><span class="pun">:</span><span class="lit">16</span><span class="pln">

WORKDIR </span><span class="pun">/</span><span class="pln">usr</span><span class="pun">/</span><span class="pln">src</span><span class="pun">/</span><span class="pln">app

COPY </span><span class="pun">--</span><span class="pln">chown</span><span class="pun">=</span><span class="pln">node</span><span class="pun">:</span><span class="pln">node </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln">
RUN npm ci 

ENV DEBUG</span><span class="pun">=</span><span class="pln">playground</span><span class="pun">:*</span><span class="pln">

USER node
CMD npm start</span></pre>

<h2>
	التمرين 12.5: إنشاء حاوية لتطبيق Node
</h2>

<p>
	يضم المستودع الذي نسخته في التمرين الأول تطبيق لائحة مهام todo-app. اطلع على الواجهة الخلفية "todo-app/todo-backend" للتطبيق واقرأ الملف "اقرأني README". لن نقترب حاليًا من الواجهة الأمامية "todo-frontend"
</p>

<p>
	الخطوة الأولى: وضع الواجهة الخلفية "todo-backend" ضمن حاوية بإنشاء الملف "todo-app/todo-backend/Dockerfile" ثم بناء الصورة.
</p>

<p>
	الخطوة الثانية: تشغيل الصورة على المنفذ الصحيح، والتأكد أن عدّاد الزيارات سيزداد عند استخدامه عبر المتصفح على العنوان "/http://localhost:3000" (أو على أي منفذ آخر قد تهيّئه).
</p>

<p>
	<strong>تلميح:</strong> شغّل التطبيق خارج الحاوية أولًا للتحقق منه قبل وضعه في الحاوية.
</p>

<h2>
	استخدام الأداة docker-compose
</h2>

<p>
	أنشأنا في جزئية سابقة من هذه السلسلة خادمًا وعلمنا أنه يعمل على المنفذ 3000 وشغّلناه باستخدام الأمر:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5689_52" style="">
<span class="pln">docker build </span><span class="pun">-</span><span class="pln">t express</span><span class="pun">-</span><span class="pln">server </span><span class="pun">.</span><span class="pln"> </span><span class="pun">&amp;&amp;</span><span class="pln"> docker run </span><span class="pun">-</span><span class="pln">p </span><span class="lit">3000</span><span class="pun">:</span><span class="lit">3000</span><span class="pln"> express</span><span class="pun">-</span><span class="pln">server</span></pre>

<p>
	ويبدو أننا سنحتاج إلى سكربت لتذكر هذه التعليمات، لكن لحسن الحظ يقدّم دوكر لنا حلًا أفضل.
</p>

<p>
	تُعد الأداة <a href="https://docs.docker.com/compose/" rel="external nofollow">Docker-compose</a> من الأدوات الرائعة الأخرى التي تساعدك على إدارة الحاوية، لهذا سنبدأ استخدام هذه الأداة خلال رحلتنا في دراسة الحاويات، إذ ستساعد على توفير بعض الوقت عند تهيئة الحاوية.
</p>

<p>
	<a href="https://docs.docker.com/compose/install" rel="external nofollow">ثبّت الأداة</a> Docker-compose ثم تأكد من عملها على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5689_54" style="">
<span class="pln">$ docker</span><span class="pun">-</span><span class="pln">compose </span><span class="pun">-</span><span class="pln">v
docker</span><span class="pun">-</span><span class="pln">compose version </span><span class="lit">1.29</span><span class="pun">.</span><span class="lit">2</span><span class="pun">,</span><span class="pln"> build </span><span class="lit">5becea4c</span></pre>

<p>
	سنحوّل الآن الأوامر السابقة إلى ملف yaml يمكن تخزينه في مستودع <a href="https://academy.hsoub.com/programming/workflow/git/%D9%85%D8%A7-%D9%87%D9%88-git%D8%9F-r1592/" rel="">غيت Git</a>.
</p>

<p>
	أنشئ الملف "docker-compose.yml" وضعه في جذر المشروع إلى جوار ملف Dockerfile، ثم ضع المحتوى التالي ضمنه:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5689_57" style="">
<span class="pln">version</span><span class="pun">:</span><span class="pln"> </span><span class="str">'3.8'</span><span class="pln">            </span><span class="pun">#</span><span class="pln"> </span><span class="pun">نسخة</span><span class="pln"> </span><span class="pun">جديدة</span><span class="pln"> </span><span class="pun">لا</span><span class="pln"> </span><span class="pun">بد</span><span class="pln"> </span><span class="pun">أن</span><span class="pln"> </span><span class="pun">تعمل</span><span class="pln">

services</span><span class="pun">:</span><span class="pln">
  app</span><span class="pun">:</span><span class="pln">                    </span><span class="pun">#</span><span class="pln"> </span><span class="pun">اسم</span><span class="pln"> </span><span class="pun">الخدمة</span><span class="pln"> </span><span class="pun">وقد</span><span class="pln"> </span><span class="pun">يكون</span><span class="pln"> </span><span class="pun">أي</span><span class="pln"> </span><span class="pun">شيء</span><span class="pln">
    image</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">-</span><span class="pln">server </span><span class="pun">#</span><span class="pln"> </span><span class="pun">صرّح</span><span class="pln"> </span><span class="pun">عن</span><span class="pln"> </span><span class="pun">الصورة</span><span class="pln"> </span><span class="pun">التي</span><span class="pln"> </span><span class="pun">تريد</span><span class="pln"> </span><span class="pun">استخدامها</span><span class="pln">
    build</span><span class="pun">:</span><span class="pln"> </span><span class="pun">.</span><span class="pln">              </span><span class="pun">#</span><span class="pln"> </span><span class="pun">حدد</span><span class="pln"> </span><span class="pun">مكان</span><span class="pln"> </span><span class="pun">بناء</span><span class="pln"> </span><span class="pun">الصورة</span><span class="pln"> </span><span class="pun">إن</span><span class="pln"> </span><span class="pun">لم</span><span class="pln"> </span><span class="pun">تكن</span><span class="pln"> </span><span class="pun">موجودة</span><span class="pln">
    ports</span><span class="pun">:</span><span class="pln">                </span><span class="pun">#</span><span class="pln"> </span><span class="pun">حدد</span><span class="pln"> </span><span class="pun">المنفذ</span><span class="pln"> </span><span class="pun">الذي</span><span class="pln"> </span><span class="pun">يرتبط</span><span class="pln"> </span><span class="pun">به</span><span class="pln"> </span><span class="pun">التطبيق</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> </span><span class="lit">3000</span><span class="pun">:</span><span class="lit">3000</span></pre>

<p>
	وضعنا شرحًا لكل سطر إلى جواره، لكن إذا أردت معرفة المواصفات الكاملة فعُد إلى <a href="https://docs.docker.com/compose/compose-file/compose-file-v3/" rel="external nofollow">التوثيق</a>.
</p>

<p>
	سنتمكن الآن من استخدام الأمر <code>docker-compose up</code> في بناء وتشغيل التطبيق، وإن أردت إعادة بناء الصور، استخدم الأمر <code>docker-compose up --build</code>.
</p>

<p>
	بإمكانك أيضًا تشغيل التطبيق في الخلفية باستخدام الأمر <code>docker-compose up -d</code> (الراية <code>d-</code> لفصل التطبيق) وإيقافه بتنفيذ الأمر <code>docker-compose down</code>.
</p>

<p>
	يُصرِّح إنشاء الملفات بهذه الطريقة عمّا تريده بدلًا من ملفات السكربت التي عليك تنفيذها وفق ترتيبٍ محدد أو عددٍ محددٍ من المرات، وهذه ممارسةٌ جيدةٌ جدًا.
</p>

<h2>
	التمرين 12.6: الأداة docker-compose
</h2>

<p>
	أنشئ الملف "todo-app/todo-backend/docker-compose.yml" الذي يعمل مع تطبيق node من التمرين السابق. وعليك الانتباه إلى عدّاد الزيارات فهو الميزة الوحيدة التي ينبغي أن تعمل.
</p>

<h2>
	استخدام الحاويات في مرحلة التطوير
</h2>

<p>
	يمكن استخدام الحاويات أثناء تطوير التطبيقات بطرق متعددة لتسهيل عملك، ومن إحدى فوائدها تجاوز تثبيت وتهيئة الأدوات مرتين.
</p>

<p>
	قد لا يكون خيارك الأفضل أن تنقل كامل بيئة التطوير إلى الحاوية، لكن إذا أردت ذلك فهذا ممكن. سنعود إلى هذه الفكرة في آخر القسم، لكن حتى ذلك الوقت عليك تشغيل تطبيق node بنفسه خارج الحاويات.
</p>

<p>
	يستخدم التطبيق الذي تعرفنا عليه في التمارين السابقة MongoDB، لهذا دعونا نستخدم <a href="https://hub.docker.com/" rel="external nofollow">Docker Hub</a> لإيجاد صورة MongoDB، إذ أنه المكان الافتراضي الذي نسحب الصور منه، كما يمكنك استخدام مسجلات أخرى أيضًا، لكن طالما أننا نتعمق في دوكر فهو خيارٌ جيد. يمكنك أن تجد من خلال بحث سريع الصورة المطلوبة على العنوان <a href="https://hub.docker.com/_/mongo." ipsnoembed="false" rel="external nofollow">https://hub.docker.com/_/mongo.</a>
</p>

<p>
	أنشئ ملف yaml يُدعى "todo-app/todo-backend/docker-compose.dev.yml" يحتوي ما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5689_59" style="">
<span class="pln">version</span><span class="pun">:</span><span class="pln"> </span><span class="str">'3.8'</span><span class="pln">

services</span><span class="pun">:</span><span class="pln">
  mongo</span><span class="pun">:</span><span class="pln">
    image</span><span class="pun">:</span><span class="pln"> mongo
    ports</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> </span><span class="lit">3456</span><span class="pun">:</span><span class="lit">27017</span><span class="pln">
    environment</span><span class="pun">:</span><span class="pln">
      MONGO_INITDB_ROOT_USERNAME</span><span class="pun">:</span><span class="pln"> root
      MONGO_INITDB_ROOT_PASSWORD</span><span class="pun">:</span><span class="pln"> example
      MONGO_INITDB_DATABASE</span><span class="pun">:</span><span class="pln"> the_database</span></pre>

<p>
	يُوضَّح معنى أول متغيري بيئة معرفين في الشيفرة السابقة في صفحة Docker Hub:
</p>

<p>
	"تُنشِئ المتغيرات المُستخدمة على التوازي مستخدمًا جديدًا وتضبط كلمة المرور له. يُنشأ هذا المستخدم في قاعدة بيانات إدارة الاستيثاق ويعطى دور الجذر root، وهو دور المستخدم الأعلى superuser."
</p>

<p>
	يخبر متغير البيئة الأخير <code>MONGO_INITDB_DATABASE</code> قاعدة البيانات MongoDB أن تنشئ قاعدة بيانات بهذا الاسم. بإمكانك استخدام الراية <code>f-</code> لتخصيص ملف لتشغيل أمر Docker Compose مثل:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5689_61" style="">
<span class="pln"> docker</span><span class="pun">-</span><span class="pln">compose </span><span class="pun">-</span><span class="pln">f docker</span><span class="pun">-</span><span class="pln">compose</span><span class="pun">.</span><span class="pln">dev</span><span class="pun">.</span><span class="pln">yml up</span></pre>

<p>
	شغًل الآن MongoDB من خلال الأمر:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5689_63" style="">
<span class="pln"> docker</span><span class="pun">-</span><span class="pln">compose </span><span class="pun">-</span><span class="pln">f docker</span><span class="pun">-</span><span class="pln">compose</span><span class="pun">.</span><span class="pln">dev</span><span class="pun">.</span><span class="pln">yml up </span><span class="pun">-</span><span class="pln">d</span></pre>

<p>
	أما الراية <code>d-</code> فلتشغيل العملية في الخلفية.
</p>

<p>
	يمكنك متابعة سجلات الخرج بتنفيذ الأمر:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5689_65" style="">
<span class="pln">docker</span><span class="pun">-</span><span class="pln">compose </span><span class="pun">-</span><span class="pln">f docker</span><span class="pun">-</span><span class="pln">compose</span><span class="pun">.</span><span class="pln">dev</span><span class="pun">.</span><span class="pln">yml logs </span><span class="pun">-</span><span class="pln">f</span></pre>

<p>
	وتُستخدم الراية <code>f-</code> للتأكد من متابعة السجلات.
</p>

<p>
	لا نحتاج حاليًا لتشغيل تطبيق Node ضمن الحاوية، فهذا أمرٌ ينطوي على قدر من التحدي، لكننا سنكتشف هذا الخيار في آخر قسم.
</p>

<p>
	نفِّذ الأمر القديم <code>npm install</code> على جهازك لإعداد تطبيق Node، ثم شغل التطبيق باستخدام متغيرات البيئة اللازمة. يمكنك تعديل الشيفرة لجعل متغيرات البيئة متغيرات افتراضية أو استخدم الملف <code>env.</code>. لا ضرر من وضع هذه المفاتيح على غيت هب لأنها تُستخدم ضمن بيئة التطوير المحلية، لذلك سأضعها هناك عن طريق الأمر <code>npm run dev</code> لمساعدتك في النسخ واللصق.
</p>

<pre class="ipsCode">
$ MONGO_URL=mongodb://localhost:3456/the_database npm run dev
</pre>

<p>
	لن يكون ذلك كافيًا، بل نحتاج إلى إنشاء مستخدم نستوثِق منه ضمن الحاوية، إذ سيقود الولوج إلى العنوان " <a href="http://localhost:3000/todos%22" ipsnoembed="false" rel="external nofollow">http://localhost:3000/todos"</a> إلى خطأ في الاستيثاق.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5689_67" style="">
<span class="pun">[</span><span class="pln">nodemon</span><span class="pun">]</span><span class="pln"> </span><span class="lit">2.0</span><span class="pun">.</span><span class="lit">12</span><span class="pln">
</span><span class="pun">[</span><span class="pln">nodemon</span><span class="pun">]</span><span class="pln"> to restart at any time</span><span class="pun">,</span><span class="pln"> enter </span><span class="pun">`</span><span class="pln">rs</span><span class="pun">`</span><span class="pln">
</span><span class="pun">[</span><span class="pln">nodemon</span><span class="pun">]</span><span class="pln"> watching path</span><span class="pun">(</span><span class="pln">s</span><span class="pun">):</span><span class="pln"> </span><span class="pun">*.*</span><span class="pln">
</span><span class="pun">[</span><span class="pln">nodemon</span><span class="pun">]</span><span class="pln"> watching extensions</span><span class="pun">:</span><span class="pln"> js</span><span class="pun">,</span><span class="pln">mjs</span><span class="pun">,</span><span class="pln">json
</span><span class="pun">[</span><span class="pln">nodemon</span><span class="pun">]</span><span class="pln"> starting </span><span class="pun">`</span><span class="pln">node </span><span class="pun">./</span><span class="pln">bin</span><span class="pun">/</span><span class="pln">www</span><span class="pun">`</span><span class="pln">
</span><span class="pun">(</span><span class="pln">node</span><span class="pun">:</span><span class="lit">37616</span><span class="pun">)</span><span class="pln"> </span><span class="typ">UnhandledPromiseRejectionWarning</span><span class="pun">:</span><span class="pln"> </span><span class="typ">MongoError</span><span class="pun">:</span><span class="pln"> command find requires authentication
    at </span><span class="typ">MessageStream</span><span class="pun">.</span><span class="pln">messageHandler </span><span class="pun">(</span><span class="str">/Users/</span><span class="pln">mluukkai</span><span class="pun">/</span><span class="pln">opetus</span><span class="pun">/</span><span class="pln">docker</span><span class="pun">-</span><span class="pln">fs</span><span class="pun">/</span><span class="pln">container</span><span class="pun">-</span><span class="pln">app</span><span class="pun">/</span><span class="pln">express</span><span class="pun">-</span><span class="pln">app</span><span class="pun">/</span><span class="pln">node_modules</span><span class="pun">/</span><span class="pln">mongodb</span><span class="pun">/</span><span class="pln">lib</span><span class="pun">/</span><span class="pln">cmap</span><span class="pun">/</span><span class="pln">connection</span><span class="pun">.</span><span class="pln">js</span><span class="pun">:</span><span class="lit">272</span><span class="pun">:</span><span class="lit">20</span><span class="pun">)</span><span class="pln">
    at </span><span class="typ">MessageStream</span><span class="pun">.</span><span class="pln">emit </span><span class="pun">(</span><span class="pln">events</span><span class="pun">.</span><span class="pln">js</span><span class="pun">:</span><span class="lit">314</span><span class="pun">:</span><span class="lit">20</span><span class="pun">)</span></pre>

<h2>
	ربط وتركيب وتهيئة قاعدة البيانات
</h2>

<p>
	ستجد في صفحة <a href="https://hub.docker.com/_/mongo" rel="external nofollow">MongoDB Docker Hub</a> تحت عنوان " تهيئة نسخة جديدة Initializing a fresh instance" معلومات عن تنفيذ شيفرة لتهيئة قاعدة البيانات ومستخدم لها.
</p>

<p>
	في المشروع التجريبي ملف "todo-app/todo-backend/mongo/mongo-init.js" يضم المحتوى التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5689_69" style="">
<span class="pln">db</span><span class="pun">.</span><span class="pln">createUser</span><span class="pun">({</span><span class="pln">
  user</span><span class="pun">:</span><span class="pln"> </span><span class="str">'the_username'</span><span class="pun">,</span><span class="pln">
  pwd</span><span class="pun">:</span><span class="pln"> </span><span class="str">'the_password'</span><span class="pun">,</span><span class="pln">
  roles</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
    </span><span class="pun">{</span><span class="pln">
      role</span><span class="pun">:</span><span class="pln"> </span><span class="str">'dbOwner'</span><span class="pun">,</span><span class="pln">
      db</span><span class="pun">:</span><span class="pln"> </span><span class="str">'the_database'</span><span class="pun">,</span><span class="pln">
    </span><span class="pun">},</span><span class="pln">
  </span><span class="pun">],</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

db</span><span class="pun">.</span><span class="pln">createCollection</span><span class="pun">(</span><span class="str">'todos'</span><span class="pun">);</span><span class="pln">

db</span><span class="pun">.</span><span class="pln">todos</span><span class="pun">.</span><span class="pln">insert</span><span class="pun">({</span><span class="pln"> text</span><span class="pun">:</span><span class="pln"> </span><span class="str">'Write code'</span><span class="pun">,</span><span class="pln"> done</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pln"> </span><span class="pun">});</span><span class="pln">
db</span><span class="pun">.</span><span class="pln">todos</span><span class="pun">.</span><span class="pln">insert</span><span class="pun">({</span><span class="pln"> text</span><span class="pun">:</span><span class="pln"> </span><span class="str">'Learn about containers'</span><span class="pun">,</span><span class="pln"> done</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">false</span><span class="pln"> </span><span class="pun">});</span></pre>

<p>
	يهيئ الملف قاعدة البيانات مع مستخدم وبعض المهام المخزّنة في القاعدة، وعلينا في الخطوة التالية نقلها إلى الحاوية وتشغيلها.
</p>

<p>
	من الممكن إنشاء صورة جديدة من mongo ثم نسخ الملف إلى الداخل، أو استخدام الأمر لتركيب الملف ضمن الحاوية، لكننا سنختار الطريقة الأخيرة.
</p>

<p>
	إن التركيب بالربط Bind mount هي عملية ربط ملف على حاسوبك المحلي بملف في الحاوية، ويمكننا تنفيذ ذلك باستخدام الراية <code>v-</code> مع الأمر بالصيغة التالية: <code>v FILE-IN-HOST:FILE-IN-CONTAINER-</code>. يُعرّف التركيب بالربط تحت المفتاح <code>volumes</code> في الملف "docker-compose"، أما صياغة التصريح فتكون على النحو التالي "مضيف ثم حاوية":
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5689_71" style="">
<span class="pln">  mongo</span><span class="pun">:</span><span class="pln">
    image</span><span class="pun">:</span><span class="pln"> mongo
    ports</span><span class="pun">:</span><span class="pln">
     </span><span class="pun">-</span><span class="pln"> </span><span class="lit">3456</span><span class="pun">:</span><span class="lit">27017</span><span class="pln">
    environment</span><span class="pun">:</span><span class="pln">
      MONGO_INITDB_ROOT_USERNAME</span><span class="pun">:</span><span class="pln"> root
      MONGO_INITDB_ROOT_PASSWORD</span><span class="pun">:</span><span class="pln"> example
      MONGO_INITDB_DATABASE</span><span class="pun">:</span><span class="pln"> the_database
    volumes</span><span class="pun">:</span><span class="pln">
     </span><span class="pun">-</span><span class="pln"> </span><span class="pun">./</span><span class="pln">mongo</span><span class="pun">/</span><span class="pln">mongo</span><span class="pun">-</span><span class="pln">init</span><span class="pun">.</span><span class="pln">js</span><span class="pun">:</span><span class="str">/docker-entrypoint-initdb.d/</span><span class="pln">mongo</span><span class="pun">-</span><span class="pln">init</span><span class="pun">.</span><span class="pln">js</span></pre>

<p>
	إن النتيجة هي أنّ الملف "mongo-init.js" في مجلد mongo على حاسوبك سيكون نفسه الملف "mongo-init.js" في المجلد "docker-entrypoint-initdb.d/" من الحاوية، وسيؤدي تعديل أحدهما إلى تعديل الآخر. لا حاجة لتغيير أي شيء أثناء التشغيل، وهذا هو مفتاح تطوير البرمجيات ضمن الحاويات.
</p>

<p>
	نفّذ الأمر التالي للتأكد من وجود كل شيء في مكانه:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5689_73" style="">
<span class="pln"> docker</span><span class="pun">-</span><span class="pln">compose </span><span class="pun">-</span><span class="pln">f docker</span><span class="pun">-</span><span class="pln">compose</span><span class="pun">.</span><span class="pln">dev</span><span class="pun">.</span><span class="pln">yml down </span><span class="pun">--</span><span class="pln">volumes </span></pre>

<p>
	ثم ابدأ لائحة جديدة بتنفيذ الأمر التالي لتهيئة قاعدة البيانات:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5689_75" style="">
<span class="pln"> docker</span><span class="pun">-</span><span class="pln">compose </span><span class="pun">-</span><span class="pln">f docker</span><span class="pun">-</span><span class="pln">compose</span><span class="pun">.</span><span class="pln">dev</span><span class="pun">.</span><span class="pln">yml up </span></pre>

<p>
	إذا واجهك خطأ على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5689_77" style="">
<span class="pln">mongo_database </span><span class="pun">|</span><span class="pln"> failed to load</span><span class="pun">:</span><span class="pln"> </span><span class="str">/docker-entrypoint-initdb.d/</span><span class="pln">mongo</span><span class="pun">-</span><span class="pln">init</span><span class="pun">.</span><span class="pln">js
mongo_database </span><span class="pun">|</span><span class="pln"> exiting </span><span class="kwd">with</span><span class="pln"> code </span><span class="pun">-</span><span class="lit">3</span></pre>

<p>
	فقد يكون لديك مشكلةً في إذن القراءة، وهذا أمرٌ قد يحدث عند التعامل مع الأقراص volumes. بإمكانك في حالتنا استخدام الأمر <code>chmod a+r mongo-init.js</code> الذي يمنح أيًا كان إمكانية قراءة الملف، لكن كن حذرًا عند استخدام التعليمة <code>chmod</code> لأن السماح بإذونات أكبر قد يقود إلى مشاكل أمنية، لهذا استخدم تلك التعليمة على الملف "mongo-init.js" الموجود على جهازك فقط.
</p>

<p>
	سيعمل تطبيق express الآن عبر متغيرات البيئة الصحيحة:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5689_79" style="">
<span class="pln">$ MONGO_URL</span><span class="pun">=</span><span class="pln">mongodb</span><span class="pun">:</span><span class="com">//the_username:the_password@localhost:3456/the_database npm run dev</span></pre>

<p>
	لنتأكد أن الطلب إلى العنوان <a href="http://localhost:3000/todos" ipsnoembed="false" rel="external nofollow">http://localhost:3000/todos</a> سيعيد كل المهام الموجودة في <a href="https://academy.hsoub.com/programming/sql/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%B9%D9%86-%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-r584/" rel="">قاعدة البيانات</a>، فمن المفترض أن يعيد المهمتان اللتان هيأناهما، ولا بد من استخدام Postman لاختبار وظائف التطبيق الأساسية مثل إضافة وحذف مهام من قاعدة البيانات.
</p>

<h2>
	البيانات المقيمة في الأقراص
</h2>

<p>
	لا تُخزّن الحاويات بياناتنا افتراضيًا، فعندما تغلق حاوية قد تستطيع أو لا تستطيع استعادة البيانات. وبصورةٍ عامة هناك طريقتان مختلفتان لتخزين البيانات:
</p>

<ul>
<li>
		التصريح عن مكان ضمن منظومة الملفات (عملية الربط بالتركيب bind mount).
	</li>
	<li>
		ترك الأمر لبرنامج دوكر كي يقرر تخزين البيانات (استخدام الأقراص volume)
	</li>
</ul>
<p>
	يُفضّل الخيار الأول عادةً في معظم الحالات التي تحتاج فيها حقًا إلى تفادي حذف البيانات، وسنرى الأسلوبين بالتطبيق العملي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5689_82" style="">
<span class="pln">services</span><span class="pun">:</span><span class="pln">
  mongo</span><span class="pun">:</span><span class="pln">
    image</span><span class="pun">:</span><span class="pln"> mongo
    ports</span><span class="pun">:</span><span class="pln">
     </span><span class="pun">-</span><span class="pln"> </span><span class="lit">3456</span><span class="pun">:</span><span class="lit">27017</span><span class="pln">
    environment</span><span class="pun">:</span><span class="pln">
      MONGO_INITDB_ROOT_USERNAME</span><span class="pun">:</span><span class="pln"> root
      MONGO_INITDB_ROOT_PASSWORD</span><span class="pun">:</span><span class="pln"> example
      MONGO_INITDB_DATABASE</span><span class="pun">:</span><span class="pln"> the_database
    volumes</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> </span><span class="pun">./</span><span class="pln">mongo</span><span class="pun">/</span><span class="pln">mongo</span><span class="pun">-</span><span class="pln">init</span><span class="pun">.</span><span class="pln">js</span><span class="pun">:</span><span class="str">/docker-entrypoint-initdb.d/</span><span class="pln">mongo</span><span class="pun">-</span><span class="pln">init</span><span class="pun">.</span><span class="pln">js
      </span><span class="pun">-</span><span class="pln"> </span><span class="pun">./</span><span class="pln">mongo_data</span><span class="pun">:</span><span class="str">/data/</span><span class="pln">db</span></pre>

<p>
	ستُنشئ الإعدادات السابقة مجلدًا يُدعى <code>mongo_data</code> ضمن منظومة الملفات في حاسوبك ثم تربطه بالحاوية بالاسم <code>data/db/</code>. أي أنّ البيانات الموجودة في المجلد <code>data/db/</code> ستُخزّن خارج الحاوية لكن بإمكانها الوصول إليها. وتذكر إضافة المجلد إلى ملف التجاهل "gitignore.".
</p>

<p>
	يمكن تحقيق الأمر ذاته باستخدام أقراص التخزين المسماة:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5689_84" style="">
<span class="pln">services</span><span class="pun">:</span><span class="pln">
  mongo</span><span class="pun">:</span><span class="pln">
    image</span><span class="pun">:</span><span class="pln"> mongo
    ports</span><span class="pun">:</span><span class="pln">
     </span><span class="pun">-</span><span class="pln"> </span><span class="lit">3456</span><span class="pun">:</span><span class="lit">27017</span><span class="pln">
    environment</span><span class="pun">:</span><span class="pln">
      MONGO_INITDB_ROOT_USERNAME</span><span class="pun">:</span><span class="pln"> root
      MONGO_INITDB_ROOT_PASSWORD</span><span class="pun">:</span><span class="pln"> example
      MONGO_INITDB_DATABASE</span><span class="pun">:</span><span class="pln"> the_database
    volumes</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> </span><span class="pun">./</span><span class="pln">mongo</span><span class="pun">/</span><span class="pln">mongo</span><span class="pun">-</span><span class="pln">init</span><span class="pun">.</span><span class="pln">js</span><span class="pun">:</span><span class="str">/docker-entrypoint-initdb.d/</span><span class="pln">mongo</span><span class="pun">-</span><span class="pln">init</span><span class="pun">.</span><span class="pln">js
      </span><span class="pun">-</span><span class="pln"> mongo_data</span><span class="pun">:</span><span class="str">/data/</span><span class="pln">db
volumes</span><span class="pun">:</span><span class="pln">
  mongo_data</span><span class="pun">:</span></pre>

<p>
	يُنشأ القرص الآن ويدار من قبل <a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%AF%D9%88%D9%83%D8%B1-docker-r607/" rel="">دوكر</a>، وبإمكانك قبل تشغيل التطبيق استعراض الأقراص الموجودة بتنفيذ الأمر <code>docker volume ls</code>، أو فحصّها <code>docker volume inspect</code>، أو حذفها <code>docker volume rm</code>. ليس اختيار مكان تخزين البيانات محليًا في هذا الخيار أمرًا قليل الأهمية موازنةً بالخيار السابق.
</p>

<h2>
	التمرين 12.7: كتابة القليل من الشيفرة للتعامل مع MongoDB
</h2>

<p>
	نفترض في هذا التمرين أنك أنجزت جميع الإعدادات التي تحدثنا عنها سابقًا بعد التمرين 12.5. وسيبقى تشغيل التطبيق خارج الحاوية وستوضع قاعدة البيانات MongoDB فقط ضمن الحاوية.
</p>

<p>
	لا توجد طريق ملائمة حتى الآن للحصول على مهمة واحدة (GET* /todos/:id*) وتحديث مهمة واحدة (PUT* /todos/:id*). جد حلًا لهاتين المشكلتين.
</p>

<h2>
	تنقيح المشاكل في الحاويات
</h2>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p>
		قد تصل أثناء كتابة الشيفرة على الأغلب إلى حالة لا يعمل فيها أي شيء. -ماتي لوكينين Matti Luukkainen.
	</p>
</blockquote>

<p>
	لا بُد من تعلم استعمال بعض الأدوات لتنقيح التطبيقات ضمن الحاويات، لأننا لا نستطيع استخدام الأمر <code>console.log</code> دائمًا. عندما تظهر الثغرات في شيفرتك، فلا بد وأن يعمل شيء ما في شيفرتك لتنطلق منه. وعمومًا هناك وضعان لتبدأ منهما: الأول هو تطبيق يعمل والثاني هو تطبيق لا يعمل، لهذا سنطلع على بعض الأدوات التي تساعد في تنقيح التطبيق في الحالة الثانية.
</p>

<p>
	يمكن أن تنتقل أثناء تطوير البرنامج في عملك خطوة خطوة لتتاكد طوال الوقت أن كل شيء يعمل كما تتوقع. لكن لا ينطبق هذا الأمر عند ضبط الإعدادات، فقد تخفق الإعدادات التي تكتبها حتى لحظة الإنتهاء منها؛ لهذا إذا كتبت ملف "docker-compose.yml" طويل أو ملف Dockerfile ولم يعمل، عليك التروي برهة والتفكير بالطرق المختلفة التي تتأكد منها أن شيء ما يعمل بالشكل المطلوب.
</p>

<p>
	لا تزال طريقة التشكيك بكل شيء واردة هنا، وكما قلنا في القسم الثالث: عليك أن تكون منظّما، وطالما أنّ المشكلة قد تكون في أي مكان، عليك التشكيك بكل شيء، وإزالة كل مصادر الخطأ واحدًا تلو الآخر. فالتوقف والتفكير بالمشكلة بدلًا من كتابة مزيدٍ من الإعدادات هي الطريقة الأنسب في الحصول على حل بسيط، كما أن البحث السريع باستخدام محركات البحث قد يساعدك في التقدم.
</p>

<h3>
	الأمر exec
</h3>

<p>
	يمكن استخدام الأمر <a href="https://docs.docker.com/engine/reference/commandline/exec/" rel="external nofollow">exec</a> للقفز مباشرةً إلى الحاوية وهي تعمل. لهذا سنهيّئ <a href="https://academy.hsoub.com/devops/servers/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%AE%D8%A7%D8%AF%D9%85-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-r574/" rel="">خادم ويب</a> في الخلفية ونجري بعض الإعدادات كي نشغّله ليعرض الرسالة "!Hello, exec" ضمن المتصفح. سنستخدم الخادم <a href="https://www.nginx.com/" rel="external nofollow">Nginx</a> القادر على تخديم الملفات الساكنة، ويدعم الملف "index.html" الذي يمثل الصفحة الافتراضية ويسمح لنا بتعديلها أو استبدالها:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5689_88" style="">
<span class="pln">$ docker container run </span><span class="pun">-</span><span class="pln">d nginx</span></pre>

<p>
	الأسئلة المطروحة الآن هي:
</p>

<ul>
<li>
		أين سنتوجه عبر المتصفح؟
	</li>
	<li>
		هل يعمل الخادم بالفعل؟
	</li>
</ul>
<p>
	لكن نعرف كيف نجيب على هذه الأسئلة وذلك باستعراض الحاويات التي تعمل:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5689_90" style="">
<span class="pln">$ docker container ls
CONTAINER ID   IMAGE           COMMAND                  CREATED              STATUS                      PORTS     NAMES
</span><span class="lit">3f831a57b7cc</span><span class="pln">   nginx           </span><span class="str">"/docker-entrypoint.…"</span><span class="pln">   </span><span class="typ">About</span><span class="pln"> a minute ago   </span><span class="typ">Up</span><span class="pln"> </span><span class="typ">About</span><span class="pln"> a minute           </span><span class="lit">80</span><span class="pun">/</span><span class="pln">tcp    keen_darwin</span></pre>

<p>
	لقد حصلنا على جواب السؤال الأول فعلًا، فيبدو أن الخادم ينصت إلى المنفذ 80. لهذا سنطفئ الخادم ثم نعيد تشغيله مستخدمين الراية <code>p-</code> للسماح للمتصفح بالوصول إليه:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5689_92" style="">
<span class="pln">$ docker container stop keen_darwin
$ docker container rm keen_darwin

$ docker container run </span><span class="pun">-</span><span class="pln">d </span><span class="pun">-</span><span class="pln">p </span><span class="lit">8080</span><span class="pun">:</span><span class="lit">80</span><span class="pln"> nginx</span></pre>

<p>
	لنلقي نظرةً على التطبيق بالانتقال إلى العنوان http://localhost:8080، وستجد أنّ التطبيق يعرض رسالة خاطئة، لهذا سنقفز إلى الحاوية مباشرةً لإصلاحها. أبقِ متصفحك مفتوحًا، فلا نريد إغلاق الحاوية عند إصلاحها، بل سنستخدم أوامر الطرفية ضمن الحاوية، ولا تنسى استخدام الراية <code>it-</code> لضمان قدرتك على التفاعل مع الحاوية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5689_94" style="">
<span class="pln">$ docker container ls
CONTAINER ID   IMAGE     COMMAND                  CREATED              STATUS              PORTS                                   NAMES
</span><span class="lit">7edcb36aff08</span><span class="pln">   nginx     </span><span class="str">"/docker-entrypoint.…"</span><span class="pln">   </span><span class="typ">About</span><span class="pln"> a minute ago   </span><span class="typ">Up</span><span class="pln"> </span><span class="typ">About</span><span class="pln"> a minute   </span><span class="lit">0.0</span><span class="pun">.</span><span class="lit">0.0</span><span class="pun">:</span><span class="lit">8080</span><span class="pun">-&gt;</span><span class="lit">80</span><span class="pun">/</span><span class="pln">tcp</span><span class="pun">,</span><span class="pln"> </span><span class="pun">:::</span><span class="lit">8080</span><span class="pun">-&gt;</span><span class="lit">80</span><span class="pun">/</span><span class="pln">tcp   wonderful_ramanujan

$ docker exec </span><span class="pun">-</span><span class="pln">it wonderful_ramanujan bash
root@7edcb36aff08</span><span class="pun">:/#</span></pre>

<p>
	بعد أن قفزنا داخل الحاوية، لا بد من إيجاد الملف الخاطئ واستبداله، إذ يتضح لنا من خلال بحث سريع باستخدام محرك بحث أن الملف هو "usr/share/nginx/html/index.html/".
</p>

<p>
	لننتقل إلى المجلد ونحذف الملف:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5689_96" style="">
<span class="pln">root@7edcb36aff08</span><span class="pun">:</span><span class="str">/# cd /</span><span class="pln">usr</span><span class="pun">/</span><span class="pln">share</span><span class="pun">/</span><span class="pln">nginx</span><span class="pun">/</span><span class="pln">html</span><span class="pun">/</span><span class="pln">
root@7edcb36aff08</span><span class="pun">:/#</span><span class="pln"> rm index</span><span class="pun">.</span><span class="pln">html</span></pre>

<p>
	لو انتقلنا الآن إلى العنوان <a href="http://localhost:8080" ipsnoembed="false" rel="external nofollow">http://localhost:8080</a> فسنحصل على الصفحة 404 لأننا حذفنا الملف الصحيح، لهذا سنستبدله بملف آخر يضم المحتوى الصحيح:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5689_100" style="">
<span class="pln">root@7edcb36aff08</span><span class="pun">:/#</span><span class="pln"> echo </span><span class="str">"Hello, exec!"</span><span class="pln"> </span><span class="pun">&gt;</span><span class="pln"> index</span><span class="pun">.</span><span class="pln">html</span></pre>

<p>
	أعد تحميل الصفحة وسترى كيف يعرض المتصفح الرسالة الصحيحة. وهكذا نرى كيف نستفيد من الأمر <code>exec</code> في التفاعل مع الحاوية. ستُفقد كل التغييرات التي أجريتها عند حذف الحاوية، ولتحفظ هذه التغييرات لا بد من دفعها باستخدام الأمر <code>commit</code>.
</p>

<h2>
	التمرين 12.8: واجهة سطر أوامر Mongo
</h2>

<p>
	استخدم <code>script</code> لتسجيل ما تفعله، ثم احفظ الملف بالاسم "script-answers/exercise12_8.txt".
</p>

<p>
	حاول الولوج إلى قاعدة البيانات في التمرين السابق أثناء تشغيل MongoDB باستخدام سطر أوامر CLI. يمكنك تنفيذ الأمر باستخدام <code>exec</code>، ثم أضف بعد ذلك مهمة جديدة عبر سطر الأوامر CLI. شغّل سطر الأوامر وأنت ضمن الحاوية باستخدام الأمر <code>mongo</code>.
</p>

<p>
	يتطلب <a href="https://academy.hsoub.com/devops/servers/%D9%85%D8%A7-%D9%87%D9%88-%D8%B3%D8%B7%D8%B1-%D8%A7%D9%84%D8%A3%D9%88%D8%A7%D9%85%D8%B1-%D8%9F-r353/" rel="">سطر أوامر</a> mongo رايتي اسم مستخدم وكلمة مرور للاستيثاق على نحوٍ صحيح،. وستعمل الرايات <code>u root -p example-</code> جيدًا، وتكون القيم مأخوذةً من الملف docker-compose.dev.yml.
</p>

<ul>
<li>
		الخطوة الأولى: شغّل MongoDB.
	</li>
	<li>
		الخطوة الثانية: استخدم الأمر <code>exec</code> للدخول إلى الحاوية.
	</li>
	<li>
		الخطوة الثالثة: افتح واجهة سطر أوامر mongo.
	</li>
	<li>
		عندما تصل إلى واجهة سطر أوامر mongo يمكنك أن تطلب منها عرض قواعد البيانات:
	</li>
</ul>
<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5689_103" style="">
<span class="pun">&gt;</span><span class="pln"> show dbs
admin         </span><span class="lit">0.000GB</span><span class="pln">
config         </span><span class="lit">0.000GB</span><span class="pln">
local         </span><span class="lit">0.000GB</span><span class="pln">
the_database  </span><span class="lit">0.000GB</span></pre>

<p>
	للولوج إلى قاعدة البيانات الصحيحة:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5689_105" style="">
<span class="pun">&gt;</span><span class="pln"> use the_database</span></pre>

<p>
	ولإيجاد المجموعات:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5689_107" style="">
<span class="pun">&gt;</span><span class="pln"> show collections
todos</span></pre>

<p>
	يمكنك الآن الوصول إلى البيانات الموجودة ضمن تلك المجموعات:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5689_109" style="">
<span class="pun">&gt;</span><span class="pln"> db</span><span class="pun">.</span><span class="pln">todos</span><span class="pun">.</span><span class="pln">find</span><span class="pun">({})</span><span class="pln">
</span><span class="pun">{</span><span class="pln"> </span><span class="str">"_id"</span><span class="pln"> </span><span class="pun">:</span><span class="pln"> </span><span class="typ">ObjectId</span><span class="pun">(</span><span class="str">"611e54b688ddbb7e84d3c46b"</span><span class="pun">),</span><span class="pln"> </span><span class="str">"text"</span><span class="pln"> </span><span class="pun">:</span><span class="pln"> </span><span class="str">"Write code"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"done"</span><span class="pln"> </span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pln"> </span><span class="pun">}</span><span class="pln">
</span><span class="pun">{</span><span class="pln"> </span><span class="str">"_id"</span><span class="pln"> </span><span class="pun">:</span><span class="pln"> </span><span class="typ">ObjectId</span><span class="pun">(</span><span class="str">"611e54b688ddbb7e84d3c46c"</span><span class="pun">),</span><span class="pln"> </span><span class="str">"text"</span><span class="pln"> </span><span class="pun">:</span><span class="pln"> </span><span class="str">"Learn about containers"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"done"</span><span class="pln"> </span><span class="pun">:</span><span class="pln"> </span><span class="kwd">false</span><span class="pln"> </span><span class="pun">}</span></pre>

<p>
	أضف مهمةً جديدة نصها: "Increase the number of tools in my toolbelt" مع ضبط حالتها على <code>false</code>. راجع التوثيق لتتعلم <a href="https://docs.mongodb.com/v4.4/reference/method/db.collection.insertOne/#mongodb-method-db.collection.insertOne" rel="external nofollow">إضافة المدخلات</a> إلى قاعدة البيانات، وتأكد من رؤية المهمة الجديدة في تطبيق Express وعند الاستعلام عنه عبر سطر أوامر Mongo CLI.
</p>

<h2>
	قاعدة البيانات Redis
</h2>

<p>
	<a href="https://redis.io/" rel="external nofollow">Redis</a> هي قاعدة بيانات من الشكل (مفتاح-قيمة) مما يجعلها قاعدة لحفظ البيانات بهيكلية أدنى من MongoDB مثلًا، فلن تجد فيها مجموعات أو جداول بل كميات من البيانات يمكن الحصول عليها وفقًا لقيم المفاتيح المرتبط بالبيانات (القيم).
</p>

<p>
	تعمل قاعدة البيانات Redis ضمن الذاكرة المؤقتة أي أنها لا تحتفظ بالبيانات دائمًا، ومن أفضل طرق الاستفادة منها هي استخدامها مثل مخزن مؤقت لتطبيق cache، إذ تُستخدم المخازن المؤقتة غالبًا لتخزين البيانات التي قد تكون بطيئة عند إحضارها وحفظها حتى تفقد صلاحيتها فيتوجب عليك إحضارها مجددًا بعد ذلك وتخزينها وهكذا.
</p>

<p>
	لا علاقة لقاعدة البيانات Redis بالحاويات لكن وطالما أننا قادرين على إضافة أي خدمة مصدرها طرف آخر إلى التطبيق، فلماذا لا نتعلم شيئًا جديدًا؟
</p>

<h2>
	التمارين 12.9 - 12.11
</h2>

<h3>
	التمرين 12.9: إعداد Redis للمشروع
</h3>

<p>
	هُيّئ خادم Express مسبقًا للتعامل مع Redis ويفتقد فقط إلى متغير البيئة <code>REDIS_URL</code>، إذ يستخدم التطبيق متغير البيئة للاتصال بقاعدة البيانات. اطلع على <a href="https://hub.docker.com/_/redis" rel="external nofollow">عمل Redis مع Docker Hub</a> ثم أضفها إلى الملف "todo-app/todo-backend/docker-compose.dev.yml" من خلال تعريف خدمة جديدة بعد mogo.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5689_112" style="">
<span class="pln">services</span><span class="pun">:</span><span class="pln">
  mongo</span><span class="pun">:</span><span class="pln">
    </span><span class="pun">...</span><span class="pln">
  redis</span><span class="pun">:</span><span class="pln">
    </span><span class="pun">???</span></pre>

<p>
	بما أن صفحة Docker Hub لا تضم جميع المعلومات الكافية، استخدم غوغل مثلًا في البحث. ستجد المنفذ الافتراضي للقاعدة بإجراء البحث الموضح في الصورة التالية:
</p>

<p style="text-align: center;">
	<img alt="01_redis_port.png" class="ipsImage ipsImage_thumbnailed" data-fileid="105123" data-unique="8ya81o3km" src="https://academy.hsoub.com/uploads/monthly_2022_08/01_redis_port.png.cc6deb9cab0d8a27d273d819ff26fe3c.png" style="width: 650px; height: auto;"></p>

<p>
	لا نعلم بعد إن كان الإعداد سيعمل ما لم نجرّب. لن يستخدم التطبيق Redis من تلقاء نفسه طبعًا، وهذا ما سنراه في التمرين التالي.
</p>

<p>
	حالما تهيئ Redis وتشغله، أعد تشغيل الواجهة الخلفية ومرر لها متغير البيئة <code>‍‍REDIS_URL</code> بالصيغة <code>redis://host:port</code>.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1282_8" style="">
<span class="pln">$ REDIS_URL</span><span class="pun">=</span><span class="pln">insert</span><span class="pun">-</span><span class="pln">redis</span><span class="pun">-</span><span class="pln">url</span><span class="pun">-</span><span class="pln">here MONGO_URL</span><span class="pun">=</span><span class="pln">mongodb</span><span class="pun">:</span><span class="com">//localhost:3456/the_database npm run dev</span></pre>

<p>
	يمكنك الآن اختبار الإعداد بإضافة السطر التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1282_10" style="">
<span class="kwd">const</span><span class="pln"> redis </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'../redis'</span><span class="pun">)</span></pre>

<p>
	إلى مثال خادم Express في الملف "routes/index.js". إن لم يحدث شيء فقد طُبِّق الإعداد بصورةٍ صحيحة وإلا سينهار الخادم:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1282_12" style="">
<span class="pln">events</span><span class="pun">.</span><span class="pln">js</span><span class="pun">:</span><span class="lit">291</span><span class="pln">
      </span><span class="kwd">throw</span><span class="pln"> er</span><span class="pun">;</span><span class="pln"> </span><span class="com">// Unhandled 'error' event</span><span class="pln">
      </span><span class="pun">^</span><span class="pln">

</span><span class="typ">Error</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Redis</span><span class="pln"> connection to localhost</span><span class="pun">:</span><span class="lit">637</span><span class="pln"> failed </span><span class="pun">-</span><span class="pln"> connect ECONNREFUSED </span><span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span><span class="pun">:</span><span class="lit">6379</span><span class="pln">
    at </span><span class="typ">TCPConnectWrap</span><span class="pun">.</span><span class="pln">afterConnect </span><span class="pun">[</span><span class="pln">as oncomplete</span><span class="pun">]</span><span class="pln"> </span><span class="pun">(</span><span class="pln">net</span><span class="pun">.</span><span class="pln">js</span><span class="pun">:</span><span class="lit">1144</span><span class="pun">:</span><span class="lit">16</span><span class="pun">)</span><span class="pln">
</span><span class="typ">Emitted</span><span class="pln"> </span><span class="str">'error'</span><span class="pln"> event on </span><span class="typ">RedisClient</span><span class="pln"> instance at</span><span class="pun">:</span><span class="pln">
    at </span><span class="typ">RedisClient</span><span class="pun">.</span><span class="pln">on_error </span><span class="pun">(</span><span class="str">/Users/</span><span class="pln">mluukkai</span><span class="pun">/</span><span class="pln">opetus</span><span class="pun">/</span><span class="pln">docker</span><span class="pun">-</span><span class="pln">fs</span><span class="pun">/</span><span class="pln">container</span><span class="pun">-</span><span class="pln">app</span><span class="pun">/</span><span class="pln">express</span><span class="pun">-</span><span class="pln">app</span><span class="pun">/</span><span class="pln">node_modules</span><span class="pun">/</span><span class="pln">redis</span><span class="pun">/</span><span class="pln">index</span><span class="pun">.</span><span class="pln">js</span><span class="pun">:</span><span class="lit">342</span><span class="pun">:</span><span class="lit">14</span><span class="pun">)</span><span class="pln">
    at </span><span class="typ">Socket</span><span class="pun">.&lt;</span><span class="pln">anonymous</span><span class="pun">&gt;</span><span class="pln"> </span><span class="pun">(</span><span class="str">/Users/</span><span class="pln">mluukkai</span><span class="pun">/</span><span class="pln">opetus</span><span class="pun">/</span><span class="pln">docker</span><span class="pun">-</span><span class="pln">fs</span><span class="pun">/</span><span class="pln">container</span><span class="pun">-</span><span class="pln">app</span><span class="pun">/</span><span class="pln">express</span><span class="pun">-</span><span class="pln">app</span><span class="pun">/</span><span class="pln">node_modules</span><span class="pun">/</span><span class="pln">redis</span><span class="pun">/</span><span class="pln">index</span><span class="pun">.</span><span class="pln">js</span><span class="pun">:</span><span class="lit">223</span><span class="pun">:</span><span class="lit">14</span><span class="pun">)</span><span class="pln">
    at </span><span class="typ">Socket</span><span class="pun">.</span><span class="pln">emit </span><span class="pun">(</span><span class="pln">events</span><span class="pun">.</span><span class="pln">js</span><span class="pun">:</span><span class="lit">314</span><span class="pun">:</span><span class="lit">20</span><span class="pun">)</span><span class="pln">
    at emitErrorNT </span><span class="pun">(</span><span class="pln">internal</span><span class="pun">/</span><span class="pln">streams</span><span class="pun">/</span><span class="pln">destroy</span><span class="pun">.</span><span class="pln">js</span><span class="pun">:</span><span class="lit">100</span><span class="pun">:</span><span class="lit">8</span><span class="pun">)</span><span class="pln">
    at emitErrorCloseNT </span><span class="pun">(</span><span class="pln">internal</span><span class="pun">/</span><span class="pln">streams</span><span class="pun">/</span><span class="pln">destroy</span><span class="pun">.</span><span class="pln">js</span><span class="pun">:</span><span class="lit">68</span><span class="pun">:</span><span class="lit">3</span><span class="pun">)</span><span class="pln">
    at processTicksAndRejections </span><span class="pun">(</span><span class="pln">internal</span><span class="pun">/</span><span class="pln">process</span><span class="pun">/</span><span class="pln">task_queues</span><span class="pun">.</span><span class="pln">js</span><span class="pun">:</span><span class="lit">80</span><span class="pun">:</span><span class="lit">21</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  errno</span><span class="pun">:</span><span class="pln"> </span><span class="pun">-</span><span class="lit">61</span><span class="pun">,</span><span class="pln">
  code</span><span class="pun">:</span><span class="pln"> </span><span class="str">'ECONNREFUSED'</span><span class="pun">,</span><span class="pln">
  syscall</span><span class="pun">:</span><span class="pln"> </span><span class="str">'connect'</span><span class="pun">,</span><span class="pln">
  address</span><span class="pun">:</span><span class="pln"> </span><span class="str">'127.0.0.1'</span><span class="pun">,</span><span class="pln">
  port</span><span class="pun">:</span><span class="pln"> </span><span class="lit">6379</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="pun">[</span><span class="pln">nodemon</span><span class="pun">]</span><span class="pln"> app crashed </span><span class="pun">-</span><span class="pln"> waiting </span><span class="kwd">for</span><span class="pln"> file changes before starting</span><span class="pun">...</span></pre>

<h3>
	التمرين 12.10
</h3>

<p>
	ثُبتت قاعدة البيانات <a href="https://www.npmjs.com/package/redis" ipsnoembed="false" rel="external nofollow">https://www.npmjs.com/package/redis</a> في المشروع مسبقًا وأضيفت إليه دالتين تعيدان وعودًا وهما <code>getAsync</code> و <code>setAsync</code>:
</p>

<ul>
<li>
		تقبل الدالة <code>setAsync</code> قيمةً ومفتاحًا، ويُستخدم المفتاح لتخزين القيمة.
	</li>
	<li>
		تقبل الدالة <code>getAsync</code> مفتاحًا وتعيد قيمته المقابلة في الوعد promise.
	</li>
</ul>
<p>
	أنجز عدادًا للمهام يخزّن عدد المهام التي تُنشئها في قاعدة البيانات Redis:
</p>

<ul>
<li>
		الخطوة الأولى: زد العداد بمقدار واحد في أي لحظة يُرسل فيها طلب لإضافة مهمة.
	</li>
	<li>
		الخطوة الثانية: أنشئ وصلة <code>GET/statics</code> ساكنة يمكنك طلب معلومات وصفية منها، وينبغي أن تُعاد بصيغة <a href="https://academy.hsoub.com/programming/javascript/%D8%AA%D8%B9%D9%84%D9%85-json-r604/" rel="">JSON</a> على النحو التالي:
	</li>
</ul>
<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1282_15" style="">
<span class="pun">{</span><span class="pln">
  </span><span class="str">"added_todos"</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span><span class="pln">
</span><span class="pun">}</span></pre>

<h3>
	التمرين 12.11
</h3>

<p>
	استخدم <code>script</code> لتسجيل ما تفعله، ثم تحفظ الملف بالاسم "script-answers/exercise12_11.txt".
</p>

<p>
	إن لم يسلك التطبيق السلوك المطلوب، فقد يساعدك الولوج المباشر إلى قاعدة البيانات في الدلالة على المشاكل. لنلقي نظرةً إذًا على طريقة استخدام <a href="https://redis.io/topics/rediscli" rel="external nofollow">واجهة سطر أوامر Redis</a> في الوصول إلى قاعدة البيانات:
</p>

<ul>
<li>
		انتقل إلى حاوية redis باستخدام الأمر <code>docker exec</code>، ثم افتح الواجهة redis-cli.
	</li>
	<li>
		ابحث عن المفتاح الذي استخدمته باستخدام الأمر <a href="https://redis.io/commands/keys" rel="external nofollow"><code>* KEYS</code></a>.
	</li>
	<li>
		تحقق من قيمة المفتاح من خلال الأمر <a href="https://redis.io/commands/get" rel="external nofollow"><code>GET</code></a>.
	</li>
	<li>
		راجع <a href="https://redis.io/commands/" rel="external nofollow">أوامر redis-cli</a> للبحث عن الأمر المناسب لضبط قيمة العداد على 9001.
	</li>
	<li>
		تأكد من أن القيمة الجديدة ستعمل عند تحديث الصفحة <a href="http://localhost:3000/statistics." ipsnoembed="false" rel="external nofollow">http://localhost:3000/statistics.</a>
	</li>
	<li>
		احذف المفتاح باستخدام واجهة سطر الأوامر وتأكد أن العداد سيعمل عند إضافة مهام جديدة.
	</li>
</ul>
<h2>
	الذاكرة المقيمة وقاعدة البيانات Redis
</h2>

<p>
	أشرنا سابقًا أنّ قاعدة البيانات Redis لا تحتفظ بالبيانات افتراضيًا، لكنه أمر يمكن حله بسهولة، إذ كل ما علينا فعله هو تشغيل Redis بأمر مختلف كما توضح صفحة <a href="https://hub.docker.com/_/redis" rel="external nofollow">Docker hub</a>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1282_17" style="">
<span class="pln">services</span><span class="pun">:</span><span class="pln">
  redis</span><span class="pun">:</span><span class="pln">
    </span><span class="pun">#</span><span class="pln"> </span><span class="pun">أي</span><span class="pln"> </span><span class="pun">شيء</span><span class="pln"> </span><span class="pun">آخر</span><span class="pln">
    command</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="str">'redis-server'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'--appendonly'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'yes'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">#</span><span class="pln"> CMD </span><span class="pun">تعديل</span><span class="pln"> 
    volumes</span><span class="pun">:</span><span class="pln"> </span><span class="pun">#</span><span class="pln"> </span><span class="pun">التصريح</span><span class="pln"> </span><span class="pun">عن</span><span class="pln"> </span><span class="pun">القرص</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> </span><span class="pun">./</span><span class="pln">redis_data</span><span class="pun">:/</span><span class="pln">data</span></pre>

<p>
	ستقيم البيانات الآن في المجلد على الجهاز المُضيف، وتذكّر إضافة المجلد إلى الملف <code>gitignore.</code>.
</p>

<h3>
	إمكانات أخرى لقاعدة البيانات Redis
</h3>

<p>
	تقدم Redis عدّة ميزات إضافةً إلى عمليات إضافة وضبط وحذف المفاتيح، فهي تحدد مثلًا فترة صلاحية المفتاح وهذه ميزة شديدة الأهمية عند استعمالها مثل مخزن مؤقت.
</p>

<p>
	كما يمكن استخدام Redis في إنجاز نماذج نشر-اشتراك <a href="https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern" rel="external nofollow">publish-subscribe</a> -أواختصارًا PubSub-، وهي آلية تواصل غير متزامنة للتطبيقات الموزّعة. تعمل Redis في هذه الحالة مثل وسيط بين تطبيقين أو أكثر، ينشر بعضها رسائل بإرسالها إلى Redis وعند وصول هذه الرسائل تُبلغ Redis جميع الأطراف بأنها اشتركت بهذه الرسائل.
</p>

<h2>
	التمرين 12.12 البيانات المقيمة و Redis
</h2>

<p>
	تأكد أن البيانات لا تُخزّن افتراضيًا في قاعدة البيانات Redis بأن تكون قيمة العداد 0 بعد تنفيذ الأمرين <code>docker-compose -f docker-compose.dev.yml down</code> و <code>docker-compose -f docker-compose.dev.yml up</code>.
</p>

<p>
	أنشئ بعد ذلك قرصًا للبيانات عن طريق تعديل الملف "todo-app/todo-backend/docker-compose.dev.yml"، وتأكد من بقاء البيانات بعد تنفيذ الأمرين <code>docker-compose -f docker-compose.dev.yml down</code> و<code>docker-compose -f docker-compose.dev.yml up</code>.
</p>

<p>
	ترجمة -وبتصرف- للفصل <a href="https://fullstackopen.com/en/part12/building_and_configuring_environments" rel="external nofollow">Building and Configuring Environments</a> من سلسلة <a href="https://fullstackopen.com/en/" rel="external nofollow">Deep Dive Into Modern Web Development</a>
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		المقال السابق: <a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D8%AD%D8%A7%D9%88%D9%8A%D8%A7%D8%AA-r641/" rel="">مدخل إلى الحاويات</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D8%AD%D8%A7%D9%88%D9%8A%D8%A7%D8%AA-r641/" rel="">أبرز المفاهيم التي يجب عليك الإلمام بها عن الحاويات</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/php/%D8%AD%D8%A7%D9%88%D9%8A%D8%A9-%D8%AF%D9%88%D9%83%D8%B1-docker-%D9%88%D9%85%D8%AE%D8%B2%D9%86-apcu-%D9%81%D9%8A-php-r1200/" rel="">حاوية دوكر Docker ومخزن APCu في PHP</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">642</guid><pubDate>Sun, 18 Sep 2022 11:10:06 +0000</pubDate></item><item><title>&#x645;&#x62F;&#x62E;&#x644; &#x625;&#x644;&#x649; &#x627;&#x644;&#x62D;&#x627;&#x648;&#x64A;&#x627;&#x62A;</title><link>https://academy.hsoub.com/devops/cloud-computing/docker/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D8%AD%D8%A7%D9%88%D9%8A%D8%A7%D8%AA-r641/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_08/62fb6cc7be57a_---.png.7506e6152c7307f485f1fdb9fdc5ab09.png" /></p>

<p>
	تتمتع البرمجيات بدورة حياة كاملة ابتداءً من التصورات الأولية وانتقالًا إلى البرمجة ومنها إلى إصدار البرنامج إلى المستخدم النهائي وصيانته. سنتعرف في هذا المقال على <a href="https://academy.hsoub.com/devops/cloud-computing/%D8%A3%D8%A8%D8%B1%D8%B2-%D8%A7%D9%84%D9%85%D9%81%D8%A7%D9%87%D9%8A%D9%85-%D8%A7%D9%84%D8%AA%D9%8A-%D9%8A%D8%AC%D8%A8-%D8%B9%D9%84%D9%8A%D9%83-%D8%A7%D9%84%D8%A5%D9%84%D9%85%D8%A7%D9%85-%D8%A8%D9%87%D8%A7-%D8%B9%D9%86-%D8%A7%D9%84%D8%AD%D8%A7%D9%88%D9%8A%D8%A7%D8%AA-r561/" rel="">مفهوم الحاويات Containers</a>، وهي أداةٌ عصرية تُستخدم في المراحل النهائية من دورة حياة البرنامج. تُغلّف الحاويات تطبيقك ضمن حزمةٍ واحدة تتضمن كل الاعتماديات التي يحتاجها التطبيق، وتكون النتيجة حاويةً يمكن أن تعمل بصورةٍ مستقل عن غيرها من الحاويات.
</p>

<p>
	تمنع الحاويات التطبيق من الوصول واستخدام ملفات وموارد الأجهزة، وقد يعطي مطورو التطبيقات بعض الأذونات للبرنامج الذي تضمه الحاوية في الوصول إلى ملفات محددة وتخصيص موارد محددة أيضًا. ولنكون أكثر دقة، تُعدّ الحاويات بمثابة بيئات عمل افتراضية على مستوى <a href="https://academy.hsoub.com/files/24-%D8%A3%D9%86%D8%B8%D9%85%D8%A9-%D8%A7%D9%84%D8%AA%D8%B4%D8%BA%D9%8A%D9%84-%D9%84%D9%84%D9%85%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D9%86/" rel="">نظام التشغيل</a>، وأكثر ما يشابهها من الناحية التقنية هي <a href="https://academy.hsoub.com/devops/linux/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-kvm-%D9%88%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%A2%D9%84%D8%A7%D8%AA-%D8%A7%D9%81%D8%AA%D8%B1%D8%A7%D8%B6%D9%8A%D8%A9-virtual-machines-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85%D9%87-%D8%B9%D9%84%D9%89-%D8%A3%D9%88%D8%A8%D9%88%D9%86%D8%AA%D9%88-r286/" rel="">الآلات الافتراضية Virtual machines</a> التي تساعد على تشغيل عدة أنظمة تشغيل على جهاز فيزيائي واحد. وينبغي على الآلات الافتراضية تشغيل النظام بأكمله، بينما تُشغّل الحاوية التطبيق بالاستفادة من نظام التشغيل الذي يستضيفها. وهكذا سيكون الاختلاف بين الحاويات والآلات الافتراضية هي أنك لن تلاحظ إلا نادرًا جدًا زيادة في تحميل النظام عند استخدام الحاويات، فهي عادةً ما تحتاج إلى تنفيذ عملية واحدة.
</p>

<p>
	يمكن للحاويات أن تكون سريعةً وقابلةً للتوسع لكونها خفيفة الوزن موازنةً بالآلات الافتراضية على الأقل، وطالما أنها تعزل التطبيق الذي يُنفَّذ ضمنها، سيساعد ذلك في الحصول على نفس الأداء تمامًا وفي أي مكان. وبالنتيجة، تُعد الحاويات خيارًا ممتازًا في أي بيئة تشغيل سحابية أو للتطبيقات التي يكثر مُستخدميها.
</p>

<p>
	تدعم معظم الخدمات السحابية، مثل AWS و Google Cloud و Microsoft Azure الحاويات وبأشكال متعددة، بما في ذلك AWS Fargate و Google Cloud Run وهما خدمتان لتشغيل الحاويات دون خادم serverless إذ لا حاجة مطلقًا لتنفيذ الحاوية إن لم تُستخدم. يمكن أيضًا تثبيت مقومات تشغيل البيئة على أغلب الأجهزة وتشغيل الحاويات بنفسك، بما في ذلك حاسوبك الخاص.
</p>

<p>
	طالما أن الحاويات تعمل في البيئات السحابية وحتى أثناء تطوير التطبيقات، فما الفائدة من الفعلية منها إذًا؟ إليك حالتي الاستخدام التاليتين:
</p>

<ul>
<li>
		<strong>الحالة الأولى</strong>: عندما تحاول أن تطور تطبيقًا من المفترض أن يعمل على نفس الجهاز لدعم إصدار أقدم، فكلاهما يحتاج إلى تثبيت نسخ مختلفة من <a href="https://academy.hsoub.com/programming/javascript/nodejs/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-nodejs-r1463/" rel="">Node.js</a>. بإمكانك استخدام nvm أو آلة افتراضية أو حتى اختراع طريقة سحرية لجعل النسختين تعملان معًا، لكن تبقى الحاويات حلًا ممتازًا لأنك تستطيع تشغيل كلا التطبيقين كلًا ضمن حاويته الخاصة، فهما معزولان عن بعضهما ولا يتداخلان.
	</li>
	<li>
		<strong>الحالة الثانية</strong>: عندما يعمل تطبيقك على حاسوبك وتحاول نقله إلى <a href="https://academy.hsoub.com/devops/servers/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%AE%D8%A7%D8%AF%D9%85-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-r574/" rel="">خادم</a>، فمن الأمور الشائعة ألا يعمل التطبيق على الخادم علمًا أنه يعمل جيدًا على حاسوبك. قد يكون الأمر اعتماديات مفقودة، أو اختلافات أخرى في بيئات التشغيل. في حالة كهذه، تظهر الحاويات مثل حل رائع، لأنها قادرة على تنفيذ التطبيق في نفس بيئة التنفيذ على حاسوبك أو على الخادم. وقد لا يكون حلًا مثاليًا نظرًا لاختلاف العتاد الصلب، لكن يمكنك أن تجعل الاختلافات في بيئات التنفيذ محدودةً جدًا.
	</li>
</ul>
<p>
	قد تسمع أحيانًا عبارات من قبيل "يعمل ضمن حاويتي"، إذ تصف هذه العبارة الحالة التي يعمل فيها التطبيق جيدًا ضمن حاوية على جهازك لكنها تُخفق في تنفيذ التطبيق عندما تنقلها إلى الخادم. هذه العبارة هي تلاعب في العبارة "يعمل على جهازي"، فالحاويات مصممةٌ لتعمل، وغالبًا ما تكون هذه الحالة خطأ في الاستخدام.
</p>

<h2>
	حول هذا القسم
</h2>

<p>
	لن نركز اهتمامنا في هذا القسم على <a href="https://academy.hsoub.com/programming/javascript/%D9%86%D9%85%D8%B7-%D9%83%D8%AA%D8%A7%D8%A8%D8%A9-%D8%B4%D9%8A%D9%81%D8%B1%D8%A9-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r785/" rel="">شيفرة جافاسكربت JavaScript</a>، بل تهيئة البيئة التي سيُنفَّذ فيها التطبيق. لهذا قد لا تحتوي التمارين أية شيفرات. كما ستجد التطبيق جاهزًا على غيت هب GitHub ويحتاج فقط إلى التهيئة. ينبغي رفع التمارين إلى مستودع GitHub واحد يضم كل الشيفرات المصدرية وإعدادات التهيئة التي نفّذتها في هذا القسم.
</p>

<p>
	لا بد أن تمتلك معرفة مبدئية بالتعامل مع Node و Express و <a href="https://academy.hsoub.com/programming/javascript/react/%D9%85%D8%A7-%D9%87%D9%8A-react%D8%9F-r773/" rel="">React</a>، وعليك إكمال الأقسام الجوهرية من 1 إلى 5 للمتابعة في هذا القسم.
</p>

<h2>
	التمرين 12.1
</h2>

<h3>
	تنبيه
</h3>

<p>
	سنخرج في هذا القسم من دائرة الراحة الخاصة بمطوري <a href="https://wiki.hsoub.com/JavaScript" rel="external">JavaScript</a>، وقد يتطلب منك ذلك جولةً لتتآلف مع أوامر الصدفة shell أو <a href="https://academy.hsoub.com/devops/servers/%D9%85%D8%A7-%D9%87%D9%88-%D8%B3%D8%B7%D8%B1-%D8%A7%D9%84%D8%A3%D9%88%D8%A7%D9%85%D8%B1-%D8%9F-r353/" rel="">سطر الأوامر command line</a> أو محث الأوامر command prompt أو الطرفية terminal قبل أن تبدأ؛ فإذا كنت تستخدم واجهة مستخدم رسومية ولم تلمس أبدًا <a href="https://academy.hsoub.com/devops/linux/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%B7%D8%B1%D9%81%D9%8A%D9%91%D8%A9-%D9%84%D9%8A%D9%86%D9%83%D8%B3-linux-terminal-r18/" rel="">طرفية لينوكس Linux</a> أو ماك Mac، وشعرت بأنك تائه لا تعرف كيف تبدأ حل التمرين، ننصحك بالاطلاع على القسم الأول من كتاب "Computing tools for CS studies"، فهو يضم كل ما تحتاجه لمتابعة العمل هنا.
</p>

<h3>
	التمرين 12.1: استخدام الحاسوب دون واجهة مستخدم رسومية
</h3>

<ul>
<li>
		الخطوة الأولى: اقرأ النص الذي يلي التنبيه.
	</li>
	<li>
		الخطوة الثانية: نزّل <a href="https://github.com/fullstack-hy2020/part12-containers-applications" rel="external nofollow">مستودع التمرين</a> واجعله مستودع تسليم التمارين لهذا القسم.
	</li>
	<li>
		الخطوة الثالثة: انتقل إلى العنوان "http://helsinki.fi" واحفظ الخرج ضمن ملف، ثم احفظ الملف ضمن مستودعك بالاسم "script-answers/exercise12_1.txt". وتذكر أنك أنشأت بالفعل المجلد "script-answers" في الخطوة السابقة.
	</li>
</ul>
<h3>
	أدوات العمل
</h3>

<p>
	تحتاج إلى بعض الأدوات الأساسية التي تختلف وفقًا لنظام التشغيل.
</p>

<ul>
<li>
		ستحتاج <a href="https://docs.microsoft.com/en-us/windows/wsl/install-win10" rel="external nofollow">WSL 2 terminal</a> على نظام ويندوز.
	</li>
	<li>
		الطرفية على نظام ماك.
	</li>
	<li>
		سطر الأوامر على نظام لينوكس.
	</li>
</ul>
<h3>
	تثبيت كل ما تحتاجه للعمل
</h3>

<p>
	سنبدأ بتثبيت البرامج التي نحتاجها، وقد تشكل هذه الخطوة إحدى العقبات المحتملة، إذ لا بد من أذونات واسعة النطاق على جهازك طالما أننا نتحدث عن شكلٍ من البيئات الافتراضية على مستوى نظام التشغيل، وهذا ما يتطلب الولوج إلى نواته.
</p>

<p>
	تتركز مادة القسم حول <a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-docker-r3/" rel="">دوكر Docker</a>، وهو يمثّل مجموعةً من المنتجات التي سنستخدمها في تشكيل وإدارة الحاويات، لكن إن لم تستطع تثبيته فلن تتمكن لسوء الحظ من متابعة العمل في هذا القسم.
</p>

<p>
	ستختلف إرشادات عملية تثبيت المنتجات على حاسوبك وفقًا لنظام التشغيل، لذلك عليك أن تجد الإرشادات الصحيحة لتثبيت المنتج من خلال صفحة <a href="https://docs.docker.com/get-docker/" rel="external nofollow">Docker</a> المخصصة للغرض، وتذكر وجود خيارات عدة للتثبيت على نفس نظام التشغيل.
</p>

<p>
	لنفترض أن كل شيء جرى على ما يُرام، لهذا سنتأكد من تطابق الإصدارات، إذ لا بُد أن يكون إصدار نسختك أعلى من هذا الإصدار:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9898_22" style="">
<span class="pln">$ docker </span><span class="pun">-</span><span class="pln">v
</span><span class="typ">Docker</span><span class="pln"> version </span><span class="lit">20.10</span><span class="pun">.</span><span class="lit">5</span><span class="pun">,</span><span class="pln"> build </span><span class="lit">55c4c88</span></pre>

<h3>
	الحاويات والصور
</h3>

<p>
	هناك مفهومان جوهريان فيما يتعلق بالحاويات، ومن السهل الخلط بينهما: الحاوية والصورة؛ إذ أن الحاوية هي نسخة التشغيل من الصورة، لهذا فكلا العبارتين التاليتين صحيحة:
</p>

<ul>
<li>
		تضم الصور الشيفرات والاعتماديات والإرشادات اللازمة لتشغيل التطبيق.
	</li>
	<li>
		تُحزّم الحاويات البرنامج ضمن وحدات معيارية.
	</li>
</ul>
<p>
	وبالتالي لا عجب من سهولة الخلط بينهما. للمساعدة في تجاوز الأمر، تُستخدم كلمة "حاوية" للإشارة إلى كلا المفهومين. لكنك لن تتمكن أبدًا من بناء حاوية أو تنزيلها لأنها موجودةٌ فقط أثناء التشغيل؛ أما الصورة فهي ملفاتٌ غير قابلة للتغيير، وبالتالي لن تكون قادرًا على تعديل صورة بعد إنشائها. لكن يمكنك استخدام صورة موجودة لإنشاء صورة جديدة بإضافة طبقات جديدة فوق القديمة. وبعبارات مجازية مستوردة من عالم الطبخ:
</p>

<ul>
<li>
		الصورة هي وجبة مطبوخة مسبقًا ومجمّدة.
	</li>
	<li>
		الحاويات هي تلك الوجبة اللذيذة الجاهزة للأكل.
	</li>
</ul>
<p>
	يُعد دوكر Docker أكثر تقنيات إنشاء الحاويات شهرةً وقد أبدع المعايير التي تستخدمها معظم تقنيات إنشاء الحاويات حاليًا. هذا المنتج تقنيًا هو مجموعة منتجات تساعد في إدارة الصور والحاويات وتمنحنا القدرة على العمل مع كل ميزاتها، إذ سيتولى Docker مثلًا مهمة تحويل الصور إلى حاويات.
</p>

<p>
	هناك أداةٌ تُدعى <a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-docker-compose-%D8%B9%D9%84%D9%89-%D8%AF%D8%A8%D9%8A%D8%A7%D9%86-r464/" rel="">Docker Compose</a> لإدارة حاويات دوكر، إذ تسمح لك بتنسيق العمل على عدة حاويات في نفس الوقت، لهذا سنستخدمها في هذا القسم لبناء بيئة تطوير محلية مركّبة، ولم يعد هناك حاجةٌ لنثبت Node عند الانتهاء من إعداد بيئة التطوير هذه. يبقى هناك مجموعةٌ من المصطلحات التي علينا أن نعرّج عليها، لكننا سنتجاهل ذلك مؤقتًا لنتعلم أكثر عن دوكر.
</p>

<p>
	لنبدأ من الأمر <code>docker container run</code> الذي يُستخدم لتشغيل الصور ضمن حاوية. لهذا الأمر الهيكلية التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9898_20" style="">
<span class="pln">container run </span><span class="pun">*</span><span class="pln">IMAGE</span><span class="pun">-</span><span class="pln">NAME</span><span class="pun">*</span></pre>

<p>
	التي تخبر دوكر بإنشاء حاويةٍ من الصورة. ومن الميزات الجيدة لهذا الأمر هو إمكانية تشغيل حاوية حتى لو لم تُنزّل الحاوية على الجهاز بعد.
</p>

<p>
	لننفِّذ الأمر:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9898_24" style="">
<span class="pun">§</span><span class="pln"> docker container run hello</span><span class="pun">-</span><span class="pln">world</span></pre>

<p>
	ستكون النتيجة العديد من المُخرجات، لهذا سنحاول فصلها إلى عدة أقسام كي نفسر هذه المخرجات معًا. رقمنا الأسطر لسهولة شرح ما فيها، فلن تجد أية أرقام لأسطر الخرج في الواقع:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9898_26" style="">
<span class="lit">1.</span><span class="pln"> </span><span class="typ">Unable</span><span class="pln"> to find image </span><span class="str">'hello-world:latest'</span><span class="pln"> locally
</span><span class="lit">2.</span><span class="pln"> latest</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Pulling</span><span class="pln"> from library</span><span class="pun">/</span><span class="pln">hello</span><span class="pun">-</span><span class="pln">world
</span><span class="lit">3.</span><span class="pln"> b8dfde127a29</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Pull</span><span class="pln"> complete
</span><span class="lit">4.</span><span class="pln"> </span><span class="typ">Digest</span><span class="pun">:</span><span class="pln"> sha256</span><span class="pun">:</span><span class="lit">5122f6204b6a3596e048758cabba3c46b1c937a46b5be6225b835d091b90e46c</span><span class="pln">
</span><span class="lit">5.</span><span class="pln"> </span><span class="typ">Status</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Downloaded</span><span class="pln"> newer image </span><span class="kwd">for</span><span class="pln"> hello</span><span class="pun">-</span><span class="pln">world</span><span class="pun">:</span><span class="pln">latest</span></pre>

<p>
	لم يعثُر الأمر على الصورة في جهازك، لذلك نزّلها من مُسجّل مجاني يُدعى <a href="https://hub.docker.com/" rel="external nofollow">Docker Hub</a>. بإمكانك الاطلاع على <a href="https://hub.docker.com/_/hello-world" rel="external nofollow">صفحة الويب الخاصة</a> بهذا المسجّل باستخدام المتصفح.
</p>

<p>
	تنص الرسالة الأولى أن الصورة "hello-world:latest" غير موجودة بعد، وهذا ما يكشف بعض التفاصيل عن الصور بحد ذاتها، إذ تتألف أسماء الصور من عدة أجزاء بصورةٍ مشابهة <a href="https://academy.hsoub.com/programming/general/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D8%B9%D9%86%D9%88%D8%A7%D9%86-url-%D9%88%D8%A3%D9%86%D9%88%D8%A7%D8%B9%D9%87-r1435/" rel="">لعناوين URL</a> وهي على الشكل:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9898_30" style="">
<span class="pln">registry</span><span class="pun">/</span><span class="pln">organisation</span><span class="pun">/</span><span class="pln">image</span><span class="pun">:</span><span class="pln">tag</span></pre>

<p>
	في حالتنا يعوَّض عن الحقول الثلاثة المفقودة بالقيم الافتراضية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9898_32" style="">
<span class="pln">index</span><span class="pun">.</span><span class="pln">docker</span><span class="pun">.</span><span class="pln">io</span><span class="pun">/</span><span class="pln">library</span><span class="pun">/</span><span class="pln">hello</span><span class="pun">-</span><span class="pln">world</span><span class="pun">:</span><span class="pln">latest</span></pre>

<p>
	يعرض السطر الثاني اسم المنظمة والمكتبة التي تحصل على الصورة منها، ويُختصر عنوان المكتبة في Docker Hub إلى <code>_</code>.
</p>

<p>
	يعرض السطران الثالث والخامس الحالة فقط، لكن ما يضمه السطر الرابع هو المهم. لكل صورة معرّف تعمية مختزل وفريد digest بناءً على الطبقات التي بُنيت منها الصورة. وفي واقع الأمر تُنشئ كل تعليمة أو خطوة استُخدمت في بناء الصورة طبقةً فريدة، لهذا يستخدم دوكر معرّف التعمية المختزل لتمييز أن الصورة بقيت كما هي.
</p>

<p>
	وهكذا نرى أن خرج الأمر السابق هو سحب ثم إخراج معلومات عن الصورة. تخبرنا الحالة بعد ذلك أن نسخةً جديدةً من الصورة "hello-world:latest" قد نُزّلت بالفعل. بإمكانك سحب الصورة باستخدام الأمر <code>docker image pull hello-world</code> ومتابعة ما سيحدث.
</p>

<p>
	يمثل التالي خرجًا ناتجًا عن حاوية، ويشرح ما يجري عند تنفيذ الأمر <code>docker container run hello-world</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9898_41" style="">
<span class="typ">Hello</span><span class="pln"> from </span><span class="typ">Docker</span><span class="pun">!</span><span class="pln">
</span><span class="typ">This</span><span class="pln"> message shows that your installation appears to be working correctly</span><span class="pun">.</span><span class="pln">

</span><span class="typ">To</span><span class="pln"> generate </span><span class="kwd">this</span><span class="pln"> message</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Docker</span><span class="pln"> took the following steps</span><span class="pun">:</span><span class="pln">
 </span><span class="lit">1.</span><span class="pln"> </span><span class="typ">The</span><span class="pln"> </span><span class="typ">Docker</span><span class="pln"> client contacted the </span><span class="typ">Docker</span><span class="pln"> daemon</span><span class="pun">.</span><span class="pln">
 </span><span class="lit">2.</span><span class="pln"> </span><span class="typ">The</span><span class="pln"> </span><span class="typ">Docker</span><span class="pln"> daemon pulled the </span><span class="str">"hello-world"</span><span class="pln"> image from the </span><span class="typ">Docker</span><span class="pln"> </span><span class="typ">Hub</span><span class="pun">.</span><span class="pln">
    </span><span class="pun">(</span><span class="pln">amd64</span><span class="pun">)</span><span class="pln">
 </span><span class="lit">3.</span><span class="pln"> </span><span class="typ">The</span><span class="pln"> </span><span class="typ">Docker</span><span class="pln"> daemon created a </span><span class="kwd">new</span><span class="pln"> container from that image which runs the
    executable that produces the output you are currently reading</span><span class="pun">.</span><span class="pln">
 </span><span class="lit">4.</span><span class="pln"> </span><span class="typ">The</span><span class="pln"> </span><span class="typ">Docker</span><span class="pln"> daemon streamed that output to the </span><span class="typ">Docker</span><span class="pln"> client</span><span class="pun">,</span><span class="pln"> which sent it
    to your terminal</span><span class="pun">.</span><span class="pln">

</span><span class="typ">To</span><span class="pln"> </span><span class="kwd">try</span><span class="pln"> something more ambitious</span><span class="pun">,</span><span class="pln"> you can run an </span><span class="typ">Ubuntu</span><span class="pln"> container </span><span class="kwd">with</span><span class="pun">:</span><span class="pln">
 $ docker container run </span><span class="pun">-</span><span class="pln">it ubuntu bash

</span><span class="typ">Share</span><span class="pln"> images</span><span class="pun">,</span><span class="pln"> automate workflows</span><span class="pun">,</span><span class="pln"> and more </span><span class="kwd">with</span><span class="pln"> a free </span><span class="typ">Docker</span><span class="pln"> ID</span><span class="pun">:</span><span class="pln">
 https</span><span class="pun">:</span><span class="com">//hub.docker.com/</span><span class="pln">

</span><span class="typ">For</span><span class="pln"> more examples and ideas</span><span class="pun">,</span><span class="pln"> visit</span><span class="pun">:</span><span class="pln">
 https</span><span class="pun">:</span><span class="com">//docs.docker.com/get-started/</span></pre>

<p>
	يتضمن الخرج بعض الأشياء الجديدة التي يجب تعلّمها مثل:
</p>

<ul>
<li>
		Docker daemon: وهي خدمة في الخلفية تتحقق من عمل الحاوية.
	</li>
	<li>
		Docker client: واجهة للتفاعل مع الخدمة السابقة.
	</li>
</ul>
<p>
	لقد تفاعلنا مع الصورة الأولى وأنشأنا حاوية من هذه الصورة، وتلقينا الخرج أثناء تنفيذ الحاوية.
</p>

<h2>
	التمرين 12.2
</h2>

<p>
	لا تتطلب منك بعض هذه التمارين كتابة أية شيفرات أو إعدادات تهيئة في ملف. عليك أن تستخدم في هذا التمرين الأمر <a href="https://man7.org/linux/man-pages/man1/script.1.html" rel="external nofollow"><code>script</code></a> لتسجيل الأوامر التي استخدمتها. جرّب ذلك بتنفيذ التعليمة <code>script</code> لتبدأ التسجيل ثم الأمر <code>"echo "hello</code> لتوليد خرج ما، ومن ثم التعليمة <code>exit</code> لإيقاف التسجيل. تُسجِّل تلك التعليمات أفعالك في ملف اسمه "typescript".
</p>

<p>
	يمكنك أيضًا نسخ ولصق جميع الأوامر التي استخدمتها في ملف نصي إن لم تستطع استخدام <code>script</code>.
</p>

<h3>
	التمرين 12.2 تشغيل حاويتك الثانية
</h3>

<p>
	استخدم <code>script</code> لتسجيل ما تفعله واحفظ الملف بالاسم "script-answers/exercise12_2.txt".
</p>

<p>
	نفّذ ما يلي:
</p>

<ul>
<li>
		الخطوة الأولى: شغّل حاوية بنفس أسلوب تشغيل الحاوية، إذ ستربطك هذه الخطوة مباشرةً مع الحاوية عبر تعليمات <code>bash</code> وستكون قادرًا على الوصول إلى كل الملفات والأدوات الموجودة ضمن الحاوية. لهذا نفذ الخطوات التالية ضمن الحاوية.
	</li>
	<li>
		الخطوة الثانية: أنشئ المجلد "usr/src/app/".
	</li>
	<li>
		الخطوة الثالثة: أنشئ الملف "usr/src/app/index.js/".
	</li>
	<li>
		الخطوة الرابعة: نفّذ التعليمة <code>exit</code> للخروج من الحاوية.
	</li>
</ul>
<p>
	ابحث عن طريقة إنشاء الملفات أو المجلدات بمساعدة محركات البحث إن لزم الأمر.
</p>

<h2>
	صورة Ubuntu
</h2>

<p>
	يحتوي الأمر التالي المُستخدم في تشغيل حاوية "ubuntu":
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9898_43" style="">
<span class="pln">docker container run </span><span class="pun">-</span><span class="pln">it ubuntu bash</span></pre>

<p>
	بعض الإضافات عن أمر تشغيل الحاوية "hello-world". سنستخدم التعليمة <code>help--</code> لفهم الموضوع أكثر، وسنجتزئ بعض المعلومات التي تتعلق بحديثنا مما يرد في خرج العملية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9898_45" style="">
<span class="pln">$ docker container run </span><span class="pun">--</span><span class="pln">help

</span><span class="typ">Usage</span><span class="pun">:</span><span class="pln">  docker container run </span><span class="pun">[</span><span class="pln">OPTIONS</span><span class="pun">]</span><span class="pln"> IMAGE </span><span class="pun">[</span><span class="pln">COMMAND</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="pln">ARG</span><span class="pun">...]</span><span class="pln">
</span><span class="typ">Run</span><span class="pln"> a command in a </span><span class="kwd">new</span><span class="pln"> container

</span><span class="typ">Options</span><span class="pun">:</span><span class="pln">
  </span><span class="pun">...</span><span class="pln">
  </span><span class="pun">-</span><span class="pln">i</span><span class="pun">,</span><span class="pln"> </span><span class="pun">--</span><span class="pln">interactive                    </span><span class="typ">Keep</span><span class="pln"> STDIN open even </span><span class="kwd">if</span><span class="pln"> not attached
  </span><span class="pun">-</span><span class="pln">t</span><span class="pun">,</span><span class="pln"> </span><span class="pun">--</span><span class="pln">tty                            </span><span class="typ">Allocate</span><span class="pln"> a pseudo</span><span class="pun">-</span><span class="pln">TTY
  </span><span class="pun">...</span></pre>

<p>
	تتحقق الرايتان أو الخياران <code>it-</code> من قدرتنا على التفاعل مع الحاوية، ثم نحدد بعد ذلك أن الصورة التي نشغلها هي "ubuntu"، ثم لدينا الأمر <code>bash</code> الذي يُنفَّذ داخل الحاوية عندما نشغلها.
</p>

<p>
	يمكنك تجريب أوامر أخرى يمكن للصورة أن تُنفذها، مثل:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9898_47" style="">
<span class="pln">docker container run </span><span class="pun">--</span><span class="pln">rm ubuntu ls</span></pre>

<p>
	إذ يرتب الأمر <code>ls</code> كل الملفات في المجلد ضمن قائمة، بينما يُزيل الأمر <code>rm--</code> الحاوية بعد التنفيذ، فلا تُحذف الحاويات تلقائيًا عادةً.
</p>

<p>
	لنتابع الآن مع أول حاوية "ubuntu" من خلال الملف "index.js" داخلها. لقد توقفت الحاوية عن العمل في اللحظة التي خرجنا منها. ولاستعراض جميع الحاويات، نستخدم الأمر <code>container ls -a</code>. إذ تستعرض الراية <code>a-</code> أو <code>all--</code> كل الحاويات التي خرجنا منها توًا.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9898_50" style="">
<span class="pln">$ docker container ls </span><span class="pun">-</span><span class="pln">a
CONTAINER ID   IMAGE     COMMAND   CREATED          STATUS                            NAMES
b8548b9faec3   ubuntu    </span><span class="str">"bash"</span><span class="pln">    </span><span class="lit">3</span><span class="pln"> minutes ago    </span><span class="typ">Exited</span><span class="pln"> </span><span class="pun">(</span><span class="lit">0</span><span class="pun">)</span><span class="pln"> </span><span class="lit">6</span><span class="pln"> seconds ago          hopeful_clarke</span></pre>

<p>
	أمامنا خياران يتعلقان بالتخاطب مع الحاوية: المعرّف في العمود الأول الذي يمكن استخدامه للتفاعل مع الحاوية في أي وقت، كما تقبل معظم الأوامر تمرير اسم الحاوية إليها. لاحظ أن الحاوية في مثالنا قد ولّدت تلقائيًا باسم "hopeful_clarke".
</p>

<p>
	تُظهر حالة الحاوية أننا خرجنا منها منذ 6 ثوان ويمكنك تشغيلها مجددًا باستخدام الأمر <code>start</code> الذي يقبل معرّف الحاوية id أو اسمها:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9898_52" style="">
<span class="pln">$ docker start hopeful_clarke
hopeful_clarke</span></pre>

<p>
	يشغّل الأمر الحاوية نفسها التي عملنا عليها سابقًا، لكن انتبه إلى أننا أغفلنا لسوء الحظ استخدام الراية <code>interactive--</code> وبالتالي لن نتمكن من التفاعل معها. مع ذلك فالحاوية تعمل فعلًا، ويُظهر تنفيذ الأمر <code>container ls -a</code> ذلك، إلا أننا لا نستطيع التواصل معها:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9898_55" style="">
<span class="pln">$ docker container ls </span><span class="pun">-</span><span class="pln">a
CONTAINER ID   IMAGE     COMMAND   CREATED          STATUS                            NAMES
b8548b9faec3   ubuntu    </span><span class="str">"bash"</span><span class="pln">    </span><span class="lit">7</span><span class="pln"> minutes ago    </span><span class="typ">Up</span><span class="pln"> </span><span class="pun">(</span><span class="lit">0</span><span class="pun">)</span><span class="pln"> </span><span class="lit">15</span><span class="pln"> seconds ago            hopeful_clarke</span></pre>

<p>
	يمكنك أيضًا تجاهل الراية <code>a-</code> في الأمر السابق لعرض الحاويات التي تعمل فقط:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9898_57" style="">
<span class="pln">$ docker container ls
CONTAINER ID   IMAGE     COMMAND   CREATED          STATUS             NAMES
</span><span class="lit">8f5abc55242a</span><span class="pln">   ubuntu    </span><span class="str">"bash"</span><span class="pln">    </span><span class="lit">8</span><span class="pln"> minutes ago    </span><span class="typ">Up</span><span class="pln"> </span><span class="lit">1</span><span class="pln"> minutes       hopeful_clarke             </span></pre>

<p>
	لنُنهي عمل الحاوية باستخدام الأمر <code>kill</code> يليه اسم الحاوية أو معرّفها ثم نحاول ثانية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9898_59" style="">
<span class="pln">$ docker kill hopeful_clarke
hopeful_clarke</span></pre>

<p>
	يُرسل الأمر <a href="https://man7.org/linux/man-pages/man7/signal.7.html" rel="external nofollow">الإشارة SIGKILL</a> إلى العملية ليجبرها على التوقف، ونستطيع التأكد من حالتها بتنفيذ الأمر <code>container ls -a</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9898_61" style="">
<span class="pln">$ docker container ls </span><span class="pun">-</span><span class="pln">a
CONTAINER ID   IMAGE     COMMAND   CREATED             STATUS                     NAMES
b8548b9faec3   ubuntu     </span><span class="str">"bash"</span><span class="pln">   </span><span class="lit">26</span><span class="pln"> minutes ago      </span><span class="typ">Exited</span><span class="pln"> </span><span class="lit">2</span><span class="pln"> seconds ago       hopeful_clarke</span></pre>

<p>
	لنشغل الحاوية الآن في وضع التفاعل:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9898_63" style="">
<span class="pln">$ docker start </span><span class="pun">-</span><span class="pln">i hopeful_clarke
root@b8548b9faec3</span><span class="pun">:/#</span></pre>

<p>
	سنجري بعض التعديلات على الملف"index.js" بإضافة شيفرة جافا سكربت، لكن تنقصنا الأداة اللازمة لذلك. سيكون المحرر Nano خيارًا جيدًا حاليًا، لذلك سننزّله ونشغله. يمكنك الاطلاع على طريقة استخدام هذا المحرر بإجراء بحث بسيط ضمن أي محرك بحث.
</p>

<p>
	انتبه أنه لا حاجة لاستخدام التعليمة <code>sudo</code> فنحن فعليًا داخل المجلد الجذري:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9898_65" style="">
<span class="pln">root@b8548b9faec3</span><span class="pun">:/#</span><span class="pln"> apt</span><span class="pun">-</span><span class="kwd">get</span><span class="pln"> update
root@b8548b9faec3</span><span class="pun">:/#</span><span class="pln"> apt</span><span class="pun">-</span><span class="kwd">get</span><span class="pln"> </span><span class="pun">-</span><span class="pln">y install nano
root@b8548b9faec3</span><span class="pun">:</span><span class="str">/# nano /</span><span class="pln">usr</span><span class="pun">/</span><span class="pln">src</span><span class="pun">/</span><span class="pln">app</span><span class="pun">/</span><span class="pln">index</span><span class="pun">.</span><span class="pln">js</span></pre>

<p>
	وهكذا يكون Nano جاهزًا للاستخدام.
</p>

<h2>
	التمرينان 12.3 - 12.4
</h2>

<p>
	حاول أن تحل التمرينين التاليين:
</p>

<h3>
	التمرين 12.3: 101 Ubuntu
</h3>

<p>
	استخدم <code>script</code> لتسجيل ما تفعله واحفظ الملف بالاسم "script-answers/exercise12_3.txt" واستخدم المحرر النصي Nano لتعديل الملف "usr/src/app/index.js/" ضمن الحاوية بإضافة السطر التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9898_67" style="">
<span class="pln">console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'Hello World'</span><span class="pun">)</span></pre>

<p>
	يمكنك الاطلاع على طريقة استخدام محرر Nano بإجراء بحث بسيط ضمن أي محرك بحث.
</p>

<h3>
	التمرين 12.4: 102 Ubuntu
</h3>

<p>
	استخدم <code>script</code> لتسجيل ما تفعله واحفظ الملف بالاسم "script-answers/exercise12_4.txt" ثم ثبّت Node طالما أنك ضمن الحاوية، ثم شغِّل الملف "index" باستخدام الأمر <code>‍‍node /usr/src/app/index.js</code>.
</p>

<p>
	من الصعب أحيانًا إيجاد تعليمات واضحة لتثبيت Node لهذا إليك بعض الأوامر التي يمكنك نسخها ولصقها:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9898_69" style="">
<span class="pln">curl </span><span class="pun">-</span><span class="pln">sL https</span><span class="pun">:</span><span class="com">//deb.nodesource.com/setup_16.x | bash</span><span class="pln">
apt install </span><span class="pun">-</span><span class="pln">y nodejs</span></pre>

<p>
	لا بد أيضًا من تثبيت curl ضمن الحاوية بطريقة مشابهة لتثبيت Nano، وتأكد بعد اكتمال التثبيت من قدرتك على تنفيذ شيفرتك داخل الحاوية.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9898_71" style="">
<span class="pln">root@b8548b9faec3</span><span class="pun">:</span><span class="str">/# node /</span><span class="pln">usr</span><span class="pun">/</span><span class="pln">src</span><span class="pun">/</span><span class="pln">app</span><span class="pun">/</span><span class="pln">index</span><span class="pun">.</span><span class="pln">js
</span><span class="typ">Hello</span><span class="pln"> </span><span class="typ">World</span></pre>

<h2>
	أوامر دوكر أخرى
</h2>

<p>
	بعد أن ثبتنا Node ضمن الحاوية يمكننا تنفيذ شيفرة <a href="https://academy.hsoub.com/programming/javascript/%D8%A7%D9%84%D8%AF%D9%84%D9%8A%D9%84-%D8%A7%D9%84%D8%B3%D8%B1%D9%8A%D8%B9-%D8%A5%D9%84%D9%89-%D9%84%D8%BA%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D9%8A%D8%A8%D8%AA-javascript-r550/" rel="">جافاسكريبت JavaScript</a> داخلها. لنحاول الآن إنشاء صورة جديدة من الحاوية بعد التعديلات التي أجريناها. للأمر الصيغة التالية: "commit" + اسم أو معرف الحاوية + اسم الصورة الجديدة. يمكنك استخدام الأمر <code>container diff</code> للتحقق من التغييرات بين الصورة الأصلية والجديدة.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9898_74" style="">
<span class="pln">$ docker commit hopeful_clarke hello</span><span class="pun">-</span><span class="pln">node</span><span class="pun">-</span><span class="pln">world</span></pre>

<p>
	كما يمكنك عرض قائمة بالصور الموجودة باستخدام <code>image Is</code> على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9898_76" style="">
<span class="pln">$ docker image ls
REPOSITORY                                      TAG         IMAGE ID       CREATED         SIZE
hello</span><span class="pun">-</span><span class="pln">node</span><span class="pun">-</span><span class="pln">world                                latest      eef776183732   </span><span class="lit">9</span><span class="pln"> minutes ago   </span><span class="lit">252MB</span><span class="pln">
ubuntu                                          latest      </span><span class="lit">1318b700e415</span><span class="pln">   </span><span class="lit">2</span><span class="pln"> weeks ago     </span><span class="lit">72.8MB</span><span class="pln">
hello</span><span class="pun">-</span><span class="pln">world                                     latest      d1165f221234   </span><span class="lit">5</span><span class="pln"> months ago    </span><span class="lit">13.3kB</span></pre>

<p>
	وستتمكن من تشغيل الصورة الجديدة على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9898_78" style="">
<span class="pln">docker run </span><span class="pun">-</span><span class="pln">it hello</span><span class="pun">-</span><span class="pln">node</span><span class="pun">-</span><span class="pln">world bash
root@4d1b322e1aff</span><span class="pun">:</span><span class="str">/# node /</span><span class="pln">usr</span><span class="pun">/</span><span class="pln">src</span><span class="pun">/</span><span class="pln">app</span><span class="pun">/</span><span class="pln">index</span><span class="pun">.</span><span class="pln">js</span></pre>

<p>
	هناك عدة طرق لتحقيق النتيجة نفسها. لهذا دعونا نتحوّل إلى حل أفضل، وسنبدأ أولًا بإزالة الحاوية القديمة باستخدام الأمر <code>container rm</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9898_82" style="">
<span class="pln">$ docker container ls </span><span class="pun">-</span><span class="pln">a
CONTAINER ID   IMAGE     COMMAND   CREATED          STATUS                  NAMES
b8548b9faec3   ubuntu    </span><span class="str">"bash"</span><span class="pln">    </span><span class="lit">31</span><span class="pln"> minutes ago   </span><span class="typ">Exited</span><span class="pln"> </span><span class="pun">(</span><span class="lit">0</span><span class="pun">)</span><span class="pln"> </span><span class="lit">9</span><span class="pln"> seconds ago               hopeful_clarke

$ docker container rm hopeful_clarke
hopeful_clarke</span></pre>

<p>
	أنشئ الملف "index.js" في المجلد الحالي واكتب التعليمة <code>('console.log('Hello, World</code> ضمنها. لا حاجة للحاويات بعد، ولنتفادى أيضًا تثبيت Node.
</p>

<p>
	هناك العديد من الصور المفيدة الجاهزة للاستخدام يقدمها Docker Hub، لهذا سنستخدم الصورة " <a href="https://hub.docker.com/_/Node%22" ipsnoembed="false" rel="external nofollow">https://hub.docker.com/_/Node"</a> التي تضم Node، وعلينا فقط اختيار الإصدار المناسب. كما تساعد الراية <code>name--</code> في الأمر <code>container run</code> على تسمية الحاوية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9898_84" style="">
<span class="pln">$ docker container run </span><span class="pun">-</span><span class="pln">it </span><span class="pun">--</span><span class="pln">name hello</span><span class="pun">-</span><span class="pln">node node</span><span class="pun">:</span><span class="lit">16</span><span class="pln"> bash</span></pre>

<p>
	سننشئ الآن مجلدًا للشيفرة ضمن الحاوية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9898_88" style="">
<span class="pln">root@77d1023af893</span><span class="pun">:</span><span class="str">/# mkdir /</span><span class="pln">usr</span><span class="pun">/</span><span class="pln">src</span><span class="pun">/</span><span class="pln">app</span></pre>

<p>
	وطالما أننا ضمن الحاوية، شغّل نسخة جديدة من الطرفية ونفّذ الأمر <code>container cp</code> لنسخ الملف من مكان وجوده في جهازك إلى الحاوية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9898_86" style="">
<span class="pln">$ docker container cp </span><span class="pun">./</span><span class="pln">index</span><span class="pun">.</span><span class="pln">js hello</span><span class="pun">-</span><span class="pln">node</span><span class="pun">:</span><span class="str">/usr/</span><span class="pln">src</span><span class="pun">/</span><span class="pln">app</span><span class="pun">/</span><span class="pln">index</span><span class="pun">.</span><span class="pln">js</span></pre>

<p>
	يمكنك الآن تنفيذ الأمر ضمن الحاوية، كما يمكن تحضيرها على هيئة صورة جديدة، لكن هناك حل أفضل أيضًا. وهذا ما سنتابع البحث فيه لاحقًا.
</p>

<p>
	ترجمة -وبتصرف- للفصل <a href="https://fullstackopen.com/en/part12/introduction_to_containers" rel="external nofollow">Introduction to Containers</a> من سلسلة <a href="https://fullstackopen.com/en/" rel="external nofollow">Deep Dive Into Modern Web Development</a>
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/%D8%A3%D8%A8%D8%B1%D8%B2-%D8%A7%D9%84%D9%85%D9%81%D8%A7%D9%87%D9%8A%D9%85-%D8%A7%D9%84%D8%AA%D9%8A-%D9%8A%D8%AC%D8%A8-%D8%B9%D9%84%D9%8A%D9%83-%D8%A7%D9%84%D8%A5%D9%84%D9%85%D8%A7%D9%85-%D8%A8%D9%87%D8%A7-%D8%B9%D9%86-%D8%A7%D9%84%D8%AD%D8%A7%D9%88%D9%8A%D8%A7%D8%AA-r561/" rel="">أبرز المفاهيم التي يجب عليك الإلمام بها عن الحاويات</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/php/%D8%AD%D8%A7%D9%88%D9%8A%D8%A9-%D8%AF%D9%88%D9%83%D8%B1-docker-%D9%88%D9%85%D8%AE%D8%B2%D9%86-apcu-%D9%81%D9%8A-php-r1200/" rel="">حاوية دوكر Docker ومخزن APCu في PHP</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D8%AF%D9%88%D9%83%D8%B1-docker-%D8%B9%D9%84%D9%89-%D9%81%D9%8A%D8%AF%D9%88%D8%B1%D8%A7-%D9%84%D9%8A%D9%86%D9%83%D8%B3-r611/" rel="">كيفية تثبيت دوكر Docker على فيدورا لينكس</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">641</guid><pubDate>Mon, 12 Sep 2022 17:03:00 +0000</pubDate></item><item><title>&#x647;&#x644; &#x64A;&#x645;&#x643;&#x646; &#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x643;&#x648;&#x628;&#x64A;&#x631;&#x646;&#x64A;&#x62A;&#x64A;&#x633; Kubernetes &#x648;&#x623;&#x648;&#x628;&#x646; &#x633;&#x62A;&#x627;&#x643; OpenStack &#x645;&#x639;&#x627;&#x61F;</title><link>https://academy.hsoub.com/devops/cloud-computing/docker/%D9%87%D9%84-%D9%8A%D9%85%D9%83%D9%86-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%83%D9%88%D8%A8%D9%8A%D8%B1%D9%86%D9%8A%D8%AA%D9%8A%D8%B3-kubernetes-%D9%88%D8%A3%D9%88%D8%A8%D9%86-%D8%B3%D8%AA%D8%A7%D9%83-openstack-%D9%85%D8%B9%D8%A7%D8%9F-r613/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_05/6282baba0fb23_----Kubernetes---OpenStack-.png.02594bbc196d7c9cd9033dde3570e906.png" /></p>

<p>
	يعمل كل من أوبن ستاك OpenStack <a href="https://academy.hsoub.com/devops/cloud-computing/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-kubernetes-r467/" rel="">وكوبيرنيتيس Kubernetes</a> معًا لتسهيل عمل مديري الأنظمة sysadmins والمطورين والمستخدمين على حدٍ سواء، حيث أجاب غالبية المستخدمين في <a href="https://openinfra.dev/user-survey/" rel="external nofollow">استطلاع لمستخدمي أوبن ستاك عام 2021</a> أنهم يستعملون كوبيرنيتيس <a href="https://academy.hsoub.com/devops/deployment/%D9%85%D8%A7-%D8%A7%D9%84%D9%81%D8%B1%D9%82-%D8%A8%D9%8A%D9%86-%D8%A7%D9%84%D8%AA%D9%86%D8%B3%D9%8A%D9%82-%D9%88%D8%A7%D9%84%D8%A3%D8%AA%D9%85%D8%AA%D8%A9%D8%9F-r562/" rel="">أداة لتنسيق الحاوية</a> أو أداة PaaS (أي منصة كخدمة Platform as a Service) لإدارة تطبيقات أوبن ستاك الخاصة بهم.
</p>

<p>
	يعتمد المستخدمون على كل من أوبن ستاك وكوبيرنيتيس بعدة طرق، ويوجد بعض حالات الاستخدام النموذجية.
</p>

<h2>
	كوبيرنيتيس على أوبن ستاك
</h2>

<p>
	يجب تشغيل كوبيرنيتيس في بيئة ما ويمكن أن تكون هذه البيئة جهاز أوبن ستاك وهمي virtual machine، وبما أن أوبن ستاك مُصَمم لتشغيله على نطاق واسع فهو يتيح لعنقود كوبيرنيتيس أن يعمل بنفس الطريقة، حيث أن عنقود كوبيرنيتيس عبارة عن مجموعة من العقد التي تقوم بتشغيل تطبيقات في حاويات.
</p>

<p>
	كما توفر <a href="https://academy.hsoub.com/programming/general/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-api-r1314/" rel="">واجهات برمجة تطبيقات</a> الخاصة بأوبن ستاك طبقة تجريدية ملائمة لكوبيرنيتيس للتكامل معها.
</p>

<h2>
	أوبن ستاك في حاوية كوبيرنتيس
</h2>

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

<p>
	فيما يلي سكربت script لبدء التشغيل ويوضح السهولة التي يمكن بها إنشاء مضيف جديد ونسخة كوبيرنيتيس على أوبن ستاك:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6911_9" style="">
<span class="pln">\#</span><span class="pun">!</span><span class="str">/usr/</span><span class="pln">bin</span><span class="pun">/</span><span class="pln">env bash
CWD</span><span class="pun">=</span><span class="str">"$(pwd)"</span><span class="pln">
$</span><span class="pun">{</span><span class="pln">OSH_INFRA_PATH</span><span class="pun">:=</span><span class="str">"../openstack-helm-infra"</span><span class="pun">}</span><span class="pln">
pushd $</span><span class="pun">{</span><span class="pln">OSH_INFRA_PATH</span><span class="pun">}</span><span class="pln">
make dev</span><span class="pun">-</span><span class="pln">deploy setup</span><span class="pun">-</span><span class="pln">host
make dev</span><span class="pun">-</span><span class="pln">deploy k8s
popd</span></pre>

<h2>
	أوبن ستاك مستقل مع كوبيرنتيس
</h2>

<p>
	يوفر أوبن ستاك مزايا وخدمات كثيرة قد لا يحتاجها المستخدم كلها، يمكن في هذه الحالة تحديد بعض خدمات أوبن ستاك والاستفادة منها مع كوبيرنيتيس، فمثلًا يمتلك مزود SIG السحابي مجموعة متنوعة من الإضافات plugins لتحقيق التكامل مع عنقود كوبيرنتيس، مثل إضافات للتحكم في الدخول والمعالجة التلقائية للأخطاء وتفويض مصادقة تواصل تطبيقات الويب وإدارة المفاتيح وتخزين البيانات، فيختار منها المستخدم ما يحتاج إليه.
</p>

<p>
	كما طُوّرت العديد من خدمات أوبن ستاك لتعمل من دون متحكم الحوسبة نوفا Nova، مما يتيح للمستخدم اختيار مزود البنية التحتية لتشغيل عنقود كوبيرنيتيس والاستفادة من مزايا مزود آخر في طريقة تخزين البيانات مثلًا.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="98881" href="https://academy.hsoub.com/uploads/monthly_2022_05/6282baba01c2a_OpenStackKubernetes.PNG.c814244236b2b654821abf07467ba7e8.PNG" rel=""><img alt="OpenStack مستقل مع Kubernetes.PNG" class="ipsImage ipsImage_thumbnailed" data-fileid="98881" data-unique="dpknurd1k" src="https://academy.hsoub.com/uploads/monthly_2022_05/6282babb3514c_OpenStackKubernetes.thumb.PNG.351d291f4778ffcb10d288b9c862a9b0.PNG" style="width: 500px; height: auto;"></a>
</p>

<p>
	يتحقق الاستخدام العملي لأوبن ستاك وكوبيرنيتيس معًا عن طريق تثبيت DevStack ثم البحث عن مزيج يناسب ويُحسّن عمل المستخدم، كما قد يساهم المستخدم في التطوير وذلك باكتشافه وجود فجوة ما ولفت انتباه مجتمعي التقنيتين إليها.
</p>

<h2>
	كوبيرنيتيس على الأجهزة المزودة بخدمة Ironic
</h2>

<p>
	تُعَد أيرونيك <a href="https://wiki.openstack.org/wiki/Ironic" rel="external nofollow">Ironic </a> خدمةً تقوم بتوفير بنية تحتية مجردة أو مستقلة bare metal في أوبن ستاك والتي تعني أنه يمكن للعميل استخدام جهاز فعلي بدلًا من جهاز وهمي، كما تقوم أيرونيك بتهيئة البنية التحتية اللازمة لتشغيل الحاويات، لذلك تشغيل حاويات كوبيرنيتيس على الأجهزة المزودة بخدمة أيرونيك يكسبها مرونة في تعديل الحاوية ويساعد على التخفيف من مشاكل المشغل اليومية مثل النشر والتحديث، كما يمنح الحاوية ميزة التكامل المباشر مع موارد الحوسبة والشبكة والتخزين.
</p>

<p>
	يمكن للمشغّل operator التفاعل مع العنقود وإجراء التغييرات بسرعة أكبر في عمليات النشر التي لا يحتاج المستخدمون فيها إلى الفصل، وعادةً ما يخفي مشغل الحاوية الآني هذا الأمر عن المستخدمين كمسألة أمنية.
</p>

<h2>
	المجتمع والتقنيات مفتوحة المصدر
</h2>

<p>
	انتشر في المجتمع التقني لفترة طويلة من الزمن مفهوم خاطئ حول كوبيرنيتيس وأوبن ستاك، بأنهما سيكونان في مواجهة مستمرة ويتنافسان على الهيمنة، ولكن تغير المفهوم بسبب عملهما وتكاملهما معًا بعدة طرق مما سهل عمل المشغلات ووفر السرعة والمرونة للمستخدم، لذا يبذل مؤسسي مجتمعات كوبيرنيتيس وأوبن ستاك جهودًا حثيثة للتعاون المستمر، بالإضافة إلى عدد كبير من المطورين والموثقين والمستخدمين والمشغلين الذين يعملون جميعًا على جعل مشروعي البرنامجين أفضل وأكثر متانة، حيث تعمل فرق مشروع أوبن ستاك على تحقيق استقلال خدماتهم بالإضافة إلى ترقية خدمات النشر لتحقيق التكامل مع كوبيرنتيس كما تقوم بتنمية وتعزيز مشاريع مثل Kuryr لوصل الحاويات مع شبكات أوبن ستاك.
</p>

<p>
	على جانب كوبيرنيتيس، يعمل مزود SIG السحابي على تطوير وتحديث قائمة الإضافات المتاحة في مزود أوبن ستاك السحابي لتحقيق التكامل مع حاويات كوبيرنيتيس وتعويض النقص فيها، ويتم اختبار التكامل للتأكد من أن المشروع يعمل على بنية تحتية من أوبن ستاك.
</p>

<p>
	بسبب هذا العمل التعاوني على كلا الجانبين وتوافر أنظمة دعم بديلة لكل من أوبن ستاك وكوبيرنتيس، كل ذلك يجعل البنية التحتية أفضل وتوفر الحلول لكثير من مشاكل المستخدمين.
</p>

<p>
	ترجمة -وبتصرف- للمقال <a href="https://opensource.com/article/22/3/kubernetes-openstack" rel="external nofollow">How to use Kubernetes and OpenStack together</a> لصاحبه Kendall Nelson.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A3%D9%88%D8%A8%D9%86-%D8%B3%D8%AA%D8%A7%D9%83-openstack-r601/" rel="">مدخل إلى أوبن ستاك OpenStack</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/%D9%86%D8%B8%D8%A7%D9%85-%D9%83%D9%88%D8%A8%D9%8A%D8%B1%D9%86%D8%AA%D8%B3-kubernetes-%D9%88%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%B9%D9%85%D9%84%D9%87-r598/" rel="">نظام كوبيرنتس Kubernetes وكيفية عمله</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/%D8%A7%D9%84%D8%A7%D9%86%D8%AA%D9%82%D8%A7%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D8%AD%D9%88%D8%B3%D8%A8%D8%A9-%D8%A7%D9%84%D8%B3%D8%AD%D8%A7%D8%A8%D9%8A%D9%91%D8%A9-r458/" rel="">الانتقال إلى الحوسبة السحابيّة</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">613</guid><pubDate>Mon, 16 May 2022 21:14:05 +0000</pubDate></item><item><title>&#x645;&#x627; &#x627;&#x644;&#x641;&#x631;&#x642; &#x628;&#x64A;&#x646; &#x62F;&#x648;&#x643;&#x631; Docker &#x648;&#x643;&#x648;&#x628;&#x64A;&#x631;&#x646;&#x64A;&#x62A;&#x64A;&#x633; Kubernetes&#x61F;</title><link>https://academy.hsoub.com/devops/cloud-computing/docker/%D9%85%D8%A7-%D8%A7%D9%84%D9%81%D8%B1%D9%82-%D8%A8%D9%8A%D9%86-%D8%AF%D9%88%D9%83%D8%B1-docker-%D9%88%D9%83%D9%88%D8%A8%D9%8A%D8%B1%D9%86%D9%8A%D8%AA%D9%8A%D8%B3-kubernetes%D8%9F-r612/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_05/6282b810e67d0_----Docker--Kubernetes.png.52f74e04a7c1f8a7fe4e5e4d9f0637db.png" /></p>

<p>
	تختلف دوكر Docker عن كوبيرنيتيس Kubernetes من ناحية حالة الاستخدام ولكنهما تعملان معًا وبشكل متكامل في <a href="https://academy.hsoub.com/devops/deployment/%D9%85%D8%B1%D8%AD%D9%84%D8%A9-%D9%86%D8%B4%D8%B1-%D8%A7%D9%84%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D9%81%D9%8A-%D8%B9%D9%85%D9%84%D9%8A%D8%A9-%D8%AA%D8%B7%D9%88%D9%8A%D8%B1-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-r589/" rel="">عمليات النشر</a> المعقدة والواسعة النطاق.
</p>

<p>
	يمكن بواسطة دوكر تطوير وشحن التطبيقات داخل الحاويات (المعروفة أيضًا باسم صور دوكر)، بينما يُعتبر كوبيرنيتيس تنسيق عالي المستوى high-level orchestration لمليارات من هذه الحاويات، ولفهم عملية التنسيق أكثر، انظر مقال <a href="https://academy.hsoub.com/devops/deployment/%D9%85%D8%A7-%D8%A7%D9%84%D9%81%D8%B1%D9%82-%D8%A8%D9%8A%D9%86-%D8%A7%D9%84%D8%AA%D9%86%D8%B3%D9%8A%D9%82-%D9%88%D8%A7%D9%84%D8%A3%D8%AA%D9%85%D8%AA%D8%A9%D8%9F-r562/" rel="">الفرق بين التنسيق والأتمتة</a>.
</p>

<p>
	ستتضح في نهاية المقال الصورة الكبيرة للمنصتين وذلك بعد فهم الفكرة النظرية بالإضافة للتفاصيل العملية.
</p>

<h2>
	تعريف الحاويات containers
</h2>

<p>
	لتوضيح مزايا المنصتين من الأفضل التركيز على الأساس الذي تعتمد عليه وشرحه وهو <a href="https://academy.hsoub.com/devops/cloud-computing/%D8%A3%D8%A8%D8%B1%D8%B2-%D8%A7%D9%84%D9%85%D9%81%D8%A7%D9%87%D9%8A%D9%85-%D8%A7%D9%84%D8%AA%D9%8A-%D9%8A%D8%AC%D8%A8-%D8%B9%D9%84%D9%8A%D9%83-%D8%A7%D9%84%D8%A5%D9%84%D9%85%D8%A7%D9%85-%D8%A8%D9%87%D8%A7-%D8%B9%D9%86-%D8%A7%D9%84%D8%AD%D8%A7%D9%88%D9%8A%D8%A7%D8%AA-r561/" rel="">الحاويات containers</a>، يُستخدم مفهوم الحاويات في تطوير التطبيقات ونشرها، والحاوية عبارة عن مجموعة برمجية متكاملة unit of software تقوم بجمع التعليمات البرمجية وجميع الاعتماديات dependencies معًا بحيث يمكن تشغيل التطبيق بسرعة وموثوقية في أي بيئة حوسبة.
</p>

<p>
	يمكن وصف الحاويات بأنها آلات افتراضية ذات حجم منخفض lightweight virtual machines، إذ تتطلب <a href="https://academy.hsoub.com/devops/linux/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-kvm-%D9%88%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%A2%D9%84%D8%A7%D8%AA-%D8%A7%D9%81%D8%AA%D8%B1%D8%A7%D8%B6%D9%8A%D8%A9-virtual-machines-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85%D9%87-%D8%B9%D9%84%D9%89-%D8%A3%D9%88%D8%A8%D9%88%D9%86%D8%AA%D9%88-r286/" rel="">الآلات الافتراضية</a> محاكاة نظام التشغيل بالإضافة إلى أي تطبيق برمجي يُراد تشغيله بشكل افتراضي، الأمر الذي يجعل من الآلات الافتراضية مُستهلِكًا شرهًا للموارد لذا تم تقديم <a href="https://academy.hsoub.com/devops/linux/%D8%AF%D9%84%D9%8A%D9%84-%D9%84%D9%81%D9%87%D9%85-%D8%AD%D8%A7%D9%88%D9%8A%D8%A7%D8%AA-%D9%84%D9%8A%D9%86%D9%83%D8%B3-%D9%88%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9%D9%87%D8%A7-r594/" rel="">الحاويات بواسطة نظام التشغيل لينكس</a> لمعالجة هذه المشكلة.
</p>

<p>
	يمكن فهم الأمر ببساطة عن طريق السيناريو الآتي: إذا كان لديك نظام تشغيل لينكس يعمل على حاسوبك، فلماذا لا تنشئ نظام تشغيل جديد لكل جهاز افتراضي يستخدم نظام التشغيل هذا؟ يمكنك بدلًا من ذلك استخدام نظام التشغيل الأساسي والذي يسمى النواة kernel لكل جهاز افتراضي وبذلك يمكن للأجهزة الافتراضية تشغيل البرامج المطلوبة فقط.
</p>

<p>
	تساعد الحاويات فريق المطورين مهما كان عدده على حل مشكلات مثل الاتساق وقابلية التوسع والأمان. يمكن استخدام حاويات مثل دوكر لفصل التطبيق عن البنية التحتية الأساسية.
</p>

<h2>
	ما هو دوكر؟
</h2>

<p>
	يُستخدم دوكر من أجل فصل التطبيق عن البنية التحتية الأساسية، إذ يقوم بتجميع التعليمات البرمجية الخاصة بالتطبيق وجميع اعتمادياتها في كيان واحد قائم بذاته ومستقل يعمل على أي نظام مدعوم.
</p>

<p>
	إلى جانب كونه النظام الأساسي الأكثر شيوعًا والمعيار الفعلي لصور الحاويات container images، فإن مزايا دوكر هي نفسها فوائد التجميع ضمن الحاويات containerization.
</p>

<p>
	يتميز دوكر بأنه قابل للنقل وقابل للتطوير ويعتبر آمن لكونه معزول، وقد يكون هذا إعدادًا مختلفًا عن المُعتاد، مما يقودنا إلى بعض عيوب دوكر، إذ يعاني التجميع ضمن الحاويات من بعض العيوب، ويقول الرافضين لها أنها لا تعمل بهدوء وبطء كما يتم في بيئة مجردة bare metal والتي تُثبّت فيها الأجهزة الافتراضية مباشرةً على العتاد hardware بدلًا من تثبيتها في داخل نظام التشغيل المضيف مثل VMware و hypervisor، كما أنه لا يوفر تكامل جيد مع تطبيقات المنظومة.
</p>

<p>
	بما أن الحاوية يمكن نقلها وتعديلها، هذا ما يؤدي إلى مشاكل في التخزين الدائم persistent storage، كما يصبح أداء بعض التطبيقات سيئًا مع الحاويات خاصة إذا كانت تطبيقات أحادية المستوى monolithic applications أي يتم فيه دمج واجهة المستخدم وكود الوصول إلى البيانات في برنامج واحد من منصة واحدة.
</p>

<h2>
	ما هو كوبيرنيتيس؟
</h2>

<p>
	كوبيرنيتيس هو المعيار الحالي في أنظمة تنظيم واتساق الحاويات orchestration الذي يسهّل عمليتي إدارة الحاويات ونشرها على نطاق واسع.
</p>

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

<p>
	يتميز كوبيرنيتيس بما يلي:
</p>

<ul>
<li>
		توزيع الحمل
	</li>
	<li>
		التحزيم الآلي
	</li>
	<li>
		أنظمة ذاتية لمعالجة الأخطاء
	</li>
	<li>
		تعمل بكفاءة مع منهجيات التكامل المستمر والتوزيع المستمر CI/CD
	</li>
	<li>
		تنسيق متطور لعمليات النشر المعقدة
	</li>
</ul>
<p>
	يعاني كوبيرنيتيس من بعض العيوب، وهي المعاكس لجميع المزايا المذكورة أعلاه:
</p>

<ul>
<li>
		يمكن أن يكون حلًا مبالغًا به لكثير من الحالات باستثناء التطبيقات المعقدة وكبيرة الحجم
	</li>
	<li>
		يمكن أن يعاني من بطء خلال التنفيذ ويحتاج إلى وقت لتعلمه
	</li>
	<li>
		التنسيق المتطور الذي يتميز به يضفي تعقيدًا إضافيًا على المشروع
	</li>
</ul>
<h2>
	الفرق بين كوبيرنيتيس ودوكر
</h2>

<p>
	كما هو مشار إليه في بداية هذا المقال، فإن الفرق بين كوبيرنيتيس ودوكر واسع جدًا، وعندما يذكر أحد ما دوكر فإنه يشير عادةً إلى <a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-docker-compose-%D8%B9%D9%84%D9%89-%D8%AF%D8%A8%D9%8A%D8%A7%D9%86-r464/" rel="">دوكر كومبوز Docker Compose</a> الذي يُعتبر منتج دوكر الأساسي الذي يسمح للمستخدم بإنشاء تطبيقات فردية خاصة في حاويات. أصبح دوكر كومبوز هو المعيار لذلك يقوم الجميع بذكر مصطلح "دوكر" فقط وذلك للاختزال.
</p>

<p>
	أما كوبيرنيتيس فهو نظام تنسيق يمكن بواسطته التحكم في جميع موارد الحاوية الخاصة بالمستخدم من مستوى إدارة واحد، أنشأته غوغل Google في الأصل للمراقبة والتحكم في عدد كبير (مثل المليارات) من الحاويات وهو الآن مفتوح المصدر.
</p>

<p>
	قد تتساءل أيهما نختار: كوبيرنيتيس أم دوكر كومبوز؟
</p>

<p>
	عندما يبحث المطور ليقرر أيهما يستخدم وبعد الاطلاع على مزايا وعيوب كل منهما يمكن طرح السؤال: "لماذا لا يكون كلاهما؟" باستخدامهما معًا، سيتمكن المطور من عزل التطبيقات كنظم حاويات وتنظيم أعداد ضخمة منها بطريقة آمنة وموثوقة.
</p>

<h2>
	الفرق بين كوبيرنيتيس ودوكر سوارم Docker Swarm
</h2>

<p>
	دوكر سوارم Docker Swarm هو نظام تنسيق حاوية دوكر، لذا فإن مقارنته بنظام كوبيرنيتيس واقعية أكثر من مقارنة كوبيرنيتيس بمنتج دوكر كومبوز.
</p>

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

<h2>
	مصادر للتعرف على كوبيرنيتيس و دوكر والخدمات المصغرة
</h2>

<p>
	إليك قائمة مقالات عربية ننصحك بالإطلاع عليها للتوسع والاستزادة:
</p>

<ul>
<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/%D8%A3%D8%A8%D8%B1%D8%B2-%D8%A7%D9%84%D9%85%D9%81%D8%A7%D9%87%D9%8A%D9%85-%D8%A7%D9%84%D8%AA%D9%8A-%D9%8A%D8%AC%D8%A8-%D8%B9%D9%84%D9%8A%D9%83-%D8%A7%D9%84%D8%A5%D9%84%D9%85%D8%A7%D9%85-%D8%A8%D9%87%D8%A7-%D8%B9%D9%86-%D8%A7%D9%84%D8%AD%D8%A7%D9%88%D9%8A%D8%A7%D8%AA-r561/" rel="">أبرز المفاهيم التي يجب عليك الإلمام بها عن الحاويات</a>: يوضح هذا المقال المفاهيم التي عليك أن تعرفها عن الحاويات بالتفصيل.
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-kubernetes-r467/" rel="">مدخل إلى Kubernetes</a>: يعرّف هذا المقال منصَّة كوبيرنيتيس، ويتناول ثلاث نقاط مهمة وهي: تعريف كوبيرنيتيس، كائنات كوبيرنيتيس، مرحبا Miniku.
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-docker-r3/" rel="">تعرف على Docker</a>: هذا المقال مدخل تعريفي لماهية Docker وأهميته.
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D9%86%D8%B8%D8%B1%D8%A9-%D8%B9%D8%A7%D9%85%D9%91%D8%A9-%D8%B9%D9%84%D9%89-%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A7%D9%84%D8%AD%D8%A7%D9%88%D9%8A%D9%91%D8%A7%D8%AA-containerization-%D8%B9%D9%84%D9%89-docker-r23/" rel="">نظرة عامّة على إعداد الحاويّات containerization على Docker</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D9%85%D8%A7-%D9%87%D9%8A-%D8%B5%D9%88%D8%B1%D8%A9-%D8%A7%D9%84%D8%AD%D8%A7%D9%88%D9%8A%D8%A9-container-image%D8%9F-r587/" rel="">ما هي صورة الحاوية container image؟</a>: يشرح المقال مفهوم صورة الحاوية container image الذي يحتوي على تطبيق جاهز packaged application إضافةً إلى اعتمادياته dependencies ومعلومات حول الخدمات التي تشغله عند إقلاعها وكل ما يتعلق به.
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/%D9%86%D8%B8%D8%A7%D9%85-%D9%83%D9%88%D8%A8%D9%8A%D8%B1%D9%86%D8%AA%D8%B3-kubernetes-%D9%88%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%B9%D9%85%D9%84%D9%87-r598/" rel="">نظام كوبيرنتس Kubernetes وكيفية عمله</a>: يشرح المقال Kubernetes وهو حل مفتوح المصدر لأتمتة النشر والتوسع الديناميكي للتطبيقات المغلفة في حاويات عبر الإنترنت، ويبين كيفية عمله، كما أنه يوضح الفرق بين OpenShift وOKD.
	</li>
</ul>
<p>
	إن أردت القائمة الكاملة، فانظر قسم <a href="https://academy.hsoub.com/devops/cloud-computing/" rel="">الحوسبة السّحابية</a> وقسم <a href="https://academy.hsoub.com/devops/cloud-computing/docker/" rel="">دوكر</a> في أكاديمية حسوب.
</p>

<h2>
	بعض الأسئلة الشائعة
</h2>

<h3>
	هل يمكن استخدام كوبيرنيتيس من دون دوكر؟
</h3>

<p>
	من الناحية التقنية نعم يمكن استخدام كوبيرنيتيس من دون دوكر طالما يتوفر نوع آخر من الحاويات. هناك خياران هما containerd و Podman، ولكن تعتبر دوكر أكثر منصات الحاويات شيوعًا.
</p>

<h3>
	هل كوبيرنيتيس مجاني؟
</h3>

<p>
	نعم إنه مجاني إذا استُخدم الإصدار المفتوح المصدر بالكامل المتاح في مستودع GitHub الخاص به، وقد يفضل الكثير من المستخدمين استخدام عروض كوبيرنيتيس المجمعة التي تشمل الخدمات والمكتبات والأنظمة الأساسية والمنصات الأخرى وما إلى ذلك في عروض السحابة الكبيرة، حتى أن البعض لديه عروض مُدارة بالكامل وتكون مفيدة ولكنها أكثر تكلفة.
</p>

<h3>
	هل يجب تعلم دوكر أم كوبيرنيتيس أولا؟
</h3>

<p>
	من الجيد البدء بالجزء الأصغر من النظام: الحاوية دوكر كومبوز ثم الانتقال إلى نظام التنسيق كوبيرنيتيس، ولكن يمكن تعلّم كليهما في نفس الوقت.
</p>

<h3>
	هل مفهوم الحاويات قديم؟
</h3>

<p>
	ليس بعد، إذ لا تزال الحاويات ذات قيمة للبنى القائمة على الخدمات المصغرة microservices على الرغم من أنها كانت موجودة بشكل أساسي مع وجود الأجهزة الافتراضية. يعتقد البعض أنه سيتم استبدال الحاويات بحلول <a href="https://academy.hsoub.com/devops/cloud-computing/%D8%B3%D8%A8%D8%B9-%D9%85%D9%86%D8%B5%D8%A7%D8%AA-%D9%85%D9%81%D8%AA%D9%88%D8%AD%D8%A9-%D8%A7%D9%84%D9%85%D8%B5%D8%AF%D8%B1-%D9%84%D9%84%D8%A8%D8%AF%D8%A1-%D9%81%D9%8A-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D8%AD%D9%88%D8%B3%D8%A8%D8%A9-%D8%AE%D9%81%D9%8A%D8%A9-%D8%A7%D9%84%D8%AE%D9%88%D8%A7%D8%AF%D9%85-serverless-r408/" rel="">خفية الخوادم serverless</a> أو بدون شيفرة برمجية ولكن هذه الحالات تميل إلى حل مشاكل مغايرة على مستويات مختلفة.
</p>

<p>
	ترجمة -وبتصرف- للمقال <a href="https://cloudacademy.com/blog/kubernetes-vs-docker/" rel="external nofollow">Kubernetes vs. Docker: Differences and Use Cases</a> من فريق Cloud Academy
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/%D8%AA%D8%B9%D9%84%D9%85-%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-kubernetes-r468/" rel="">تعلم أساسيات Kubernetes</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%AF%D9%88%D9%83%D8%B1-docker-r607/" rel="">مدخل إلى دوكر Docker</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D9%85%D8%A7-%D8%B9%D9%84%D9%8A%D9%83-%D9%85%D8%B9%D8%B1%D9%81%D8%AA%D9%87-%D8%B9%D9%86-%D8%A7%D9%84%D9%81%D8%B1%D9%82-%D8%A8%D9%8A%D9%86-%D8%AF%D9%88%D9%83%D8%B1-%D9%88%D8%A7%D9%84%D8%A3%D8%AC%D9%87%D8%B2%D8%A9-%D8%A7%D9%84%D8%A7%D9%81%D8%AA%D8%B1%D8%A7%D8%B6%D9%8A%D8%A9-r456/" rel="">ما عليك معرفته عن الفرق بين دوكر والأجهزة الافتراضية</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/%D8%AA%D8%B9%D9%84%D9%85-%D8%A7%D9%84%D8%AD%D9%88%D8%B3%D8%A8%D8%A9-%D8%A7%D9%84%D8%B3%D8%AD%D8%A7%D8%A8%D9%8A%D9%91%D8%A9-%D8%A7%D9%84%D9%85%D8%AA%D8%B7%D9%84%D8%A8%D8%A7%D8%AA-%D8%A7%D9%84%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D9%91%D8%A9%D8%8C-%D9%88%D9%83%D9%8A%D9%81-%D8%AA%D8%B5%D8%A8%D8%AD-%D9%85%D9%87%D9%86%D8%AF%D8%B3-%D8%AD%D9%88%D8%B3%D8%A8%D8%A9-%D8%B3%D8%AD%D8%A7%D8%A8%D9%8A%D9%91%D8%A9-r457/" rel="">تعلم الحوسبة السحابيّة: المتطلبات الأساسيّة، وكيف تصبح مهندس حوسبة سحابيّة</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">612</guid><pubDate>Mon, 16 May 2022 20:50:59 +0000</pubDate></item><item><title>&#x643;&#x64A;&#x641;&#x64A;&#x629; &#x62A;&#x62B;&#x628;&#x64A;&#x62A; &#x62F;&#x648;&#x643;&#x631; Docker &#x639;&#x644;&#x649; &#x641;&#x64A;&#x62F;&#x648;&#x631;&#x627; &#x644;&#x64A;&#x646;&#x643;&#x633;</title><link>https://academy.hsoub.com/devops/cloud-computing/docker/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D8%AF%D9%88%D9%83%D8%B1-docker-%D8%B9%D9%84%D9%89-%D9%81%D9%8A%D8%AF%D9%88%D8%B1%D8%A7-%D9%84%D9%8A%D9%86%D9%83%D8%B3-r611/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_05/6282b45e05e7d_-----.png.c5bc79ed1affe33e404ac8c8f452b733.png" /></p>

<p>
	<a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%AF%D9%88%D9%83%D8%B1-docker-r607/" rel="">دوكر Docker</a> تقنية أساسية للمطورين ومسؤولي الأنظمة في يومنا هذا وهي عبارة عن مجموعة أدوات تعتمد في عملها على الحاويات التي تغطي مجالًا واسعًا جدًا من التطبيقات وتلعب دورًا مهمًا في العديد من المجالات. لن نذكر مزايا دوكر في هذا المقال الذي هو عبارة عن دليل لتثبيت دوكر في توزيعة فيدورا Fedora من <a href="https://academy.hsoub.com/devops/linux/%D9%85%D8%A7-%D9%87%D9%88-%D9%86%D8%B8%D8%A7%D9%85-%D8%A7%D9%84%D8%AA%D8%B4%D8%BA%D9%8A%D9%84-%D9%84%D9%8A%D9%86%D9%83%D8%B3%D8%9F-r451/" rel="">لينكس</a> فقط.
</p>

<h2>
	تثبيت دوكر على فيدورا
</h2>

<p>
	يمكن تثبيت دوكر على فيدورا بثلاث طرق:
</p>

<ul>
<li>
		التثبيت عبر DNF وتعتبر طريقة سهلة ويوصى بها.
	</li>
	<li>
		التثبيت عبر RPM.
	</li>
	<li>
		التثبيت باستخدام سكربت script (نص كود برمجي).
	</li>
</ul>
<h3>
	الطريقة الأولى: التثبيت عبر DNF
</h3>

<p>
	تعتبر الطريقة الأنسب لتثبيت دوكر، نظرًا لسهولة تثبيت الإصدارات المحدثة دون الحاجة لخطوات معقدة للتثبيت، ينبغي إضافة مستودعات دوكر الرسمية ثم تثبيت دوكر عبر أمر DNF.
</p>

<p>
	يتوجب بدايةً كتابة وتنفيذ الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9642_20" style="">
<span class="pln">sudo dnf install dnf</span><span class="pun">-</span><span class="pln">plugins</span><span class="pun">-</span><span class="pln">core </span><span class="pun">-</span><span class="pln">y</span></pre>

<p>
	ثم إضافة مستودع دوكر <a href="https://academy.hsoub.com/files/24-%D8%A3%D9%86%D8%B8%D9%85%D8%A9-%D8%A7%D9%84%D8%AA%D8%B4%D8%BA%D9%8A%D9%84-%D9%84%D9%84%D9%85%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D9%86/" rel="">لنظام تشغيل</a> Fedora باستخدام الأمر:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9642_22" style="">
<span class="pln">sudo dnf config</span><span class="pun">-</span><span class="pln">manager </span><span class="pun">--</span><span class="pln">add</span><span class="pun">-</span><span class="pln">repo https</span><span class="pun">:</span><span class="com">//download.docker.com/linux/fedora/docker-ce.repo</span></pre>

<p>
	الخطوة التالية هي تثبيت دوكر والحزم المطلوبة في النظام، والذي يتحقق بواسطة الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9642_24" style="">
<span class="pln">sudo dnf install docker</span><span class="pun">-</span><span class="pln">ce docker</span><span class="pun">-</span><span class="pln">ce</span><span class="pun">-</span><span class="pln">cli containerd</span><span class="pun">.</span><span class="pln">io</span></pre>

<p>
	توضح الصورة التالية خطوة بدء تثبيت دوكر عمليًا على النظام:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="98878" href="https://academy.hsoub.com/uploads/monthly_2022_05/6282a5662303d_.PNG.3d2aa0f5ad01347150d0b17aa2b23710.PNG" rel=""><img alt="بدء التثبيت.PNG" class="ipsImage ipsImage_thumbnailed" data-fileid="98878" data-unique="vb5s5rf7q" src="https://academy.hsoub.com/uploads/monthly_2022_05/6282a56a81404_.thumb.PNG.f419f8b4f80bb22dc75869aef6560bdb.PNG" style="width: 650px; height: auto;"></a>
</p>

<p>
	سيظهر في الخطوة التالية طلب استيراد مفتاح GPG من أجل التثبيت، يجب السماح بعملية بالاستيراد.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="98877" href="https://academy.hsoub.com/uploads/monthly_2022_05/6282a55cf284f_.PNG.1b4991937a1b7ef5e2f72593475f4ddd.PNG" rel=""><img alt="السماح بعملية الاستيراد.PNG" class="ipsImage ipsImage_thumbnailed" data-fileid="98877" data-unique="93x4wm822" src="https://academy.hsoub.com/uploads/monthly_2022_05/6282a5613bc6f_.thumb.PNG.aa861bfa7f490ab3cc1c8822bd7b833a.PNG" style="width: 650px; height: auto;"></a>
</p>

<p>
	بعد إكمال الخطوة الأخيرة سيكون docker جاهز للعمل على النظام، ويوجد خطوة اختيارية تمكن المستخدم من التحقق من عمل النظام بشكل جيد باستخدام هذا الأمر:
</p>

<pre class="ipsCode">
sudo docker run hello-world
</pre>

<h3>
	الطريقة الثانية: تثبيت دوكر عن طريق تنزيل حزمة RPM
</h3>

<p>
	تظهر فائدة هذه الطريقة في حالات محددة مثل أن توجد حاجة لاختبار إصدار محدد من دوكر أو تثبيت دوكر لإصدارات قديمة من فيدورا Fedora، ومع ذلك فإن الحاجة لتنزيل <a href="https://academy.hsoub.com/devops/linux/15-%D9%85%D8%AB%D8%A7%D9%84%D8%A7-%D9%84%D8%A5%D8%AF%D8%A7%D8%B1%D8%A9-%D8%A7%D9%84%D8%AD%D8%B2%D9%85-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-rpm-r283/" rel="">حزمة RPM</a> يدويًا في كل مرة يظهر فيها إصدار جديد يجعل هذه الطريقة غير عملية أي أن عملية التحديث بتنزيل أحدث إصدارات يجب أن تنفذها يدويًا في هذه الطريقة.
</p>

<p>
	يمكن تنزيل <a href="https://download.docker.com/linux/fedora/" rel="external nofollow">الحزمة الرسمية من الموقع</a> ثم تحديد الإصدار وتاريخ الإصدار بالإضافة لمعمارية architecture الإصدار ومن ثم تنزيله (سيحتاج المستخدم إلى الحزمة docker-ce، والحزمة docker-ce-cli بشكل أساسي، ويمكن تنزيل حزم إضافية في حال الحاجة). لتثبيت الحزم المنزلة يجب التوجه من خلال <a href="https://academy.hsoub.com/devops/servers/%D9%85%D8%A7-%D9%87%D9%88-%D8%B3%D8%B7%D8%B1-%D8%A7%D9%84%D8%A3%D9%88%D8%A7%D9%85%D8%B1-%D8%9F-r353/" rel="">سطر الأوامر</a> إلى المجلد الحاوي للحزم السابقة، ثم كتابة الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9642_29" style="">
<span class="pln">sudo dnf install </span><span class="pun">/</span><span class="pln">path</span><span class="pun">/</span><span class="pln">to</span><span class="pun">/</span><span class="pln">file</span><span class="pun">.</span><span class="pln">rpm </span><span class="pun">-</span><span class="pln">y</span></pre>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="98876" href="https://academy.hsoub.com/uploads/monthly_2022_05/6282a55081d82_RPM.PNG.dcb445e3f0cd025d5e9c6a60c1e9f104.PNG" rel=""><img alt="التثبيت عبر RPM.PNG" class="ipsImage ipsImage_thumbnailed" data-fileid="98876" data-unique="vlhc2j40v" src="https://academy.hsoub.com/uploads/monthly_2022_05/6282a55424c7d_RPM.thumb.PNG.08903db56bc252030400f5b355ec57ef.PNG" style="width: 600px; height: auto;"></a>
</p>

<h3>
	الطريقة الثالثة: تثبيت دوكر باستخدام سكربت
</h3>

<p>
	تتيح أداة دوكر كود برمجي رسمي يقوم بالخطوات اللازمة لعملية التثبيت، ولا يمكن استخدام الكود من أجل ترقية إصدار دوكر الحالي المثبت بواسطة إحدى الطريقتين السابقتين، على الرغم من ذلك من الممكن ترقية الإصدار في حال استخدام الكود مسبقًا للحصول على دوكر.
</p>

<p>
	بدايةً يجب تنزيل ملف الكود البرمجي ثم جعله قابل للتنفيذ ثم تنفيذه بصلاحيات sudo:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9642_8" style="">
<span class="pln">curl </span><span class="pun">-</span><span class="pln">fsSL https</span><span class="pun">:</span><span class="com">//get.docker.com -o get-docker.sh</span><span class="pln">
chmod u</span><span class="pun">+</span><span class="pln">x </span><span class="pun">./</span><span class="kwd">get</span><span class="pun">-</span><span class="pln">docker</span><span class="pun">.</span><span class="pln">sh
sudo sh </span><span class="pun">./</span><span class="kwd">get</span><span class="pun">-</span><span class="pln">docker</span><span class="pun">.</span><span class="pln">sh</span></pre>

<p>
	يقوم هذا الكود بالعمليات اللازمة لتثبيت دوكر.
</p>

<h2>
	اختبار دوكر بعد تثبيته
</h2>

<p>
	يمكن استخدام دوكر مباشرةً بعد التثبيت، ولكن قد تواجه المستخدم بعض المشاكل لذا يُفضّل إجراء اختبار للتأكد من عمل دوكر بشكل صحيح، ويتمثل الاختبار بتشغيل صورة hello-world وهو نسخة يقدمها دوكر لاختبار الأداء، ولإجراء الاختبار يجب أولًا تشغيل خدمة دوكر لأنها غير مُفعّلة تلقائيًا وذلك بواسطة الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9642_10" style="">
<span class="pln">sudo systemctl start docker</span></pre>

<p>
	ثانيًا: تنزيل صورة hello-world من دوكر ومن ثم تشغيله:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9642_12" style="">
<span class="pln">sudo docker run hello</span><span class="pun">-</span><span class="pln">world</span></pre>

<p>
	توضّح هذه الصورة عمل حاوية hello-world بنجاح.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="98879" href="https://academy.hsoub.com/uploads/monthly_2022_05/6282a575b3417_.PNG.95cfe5dfb7953829973386a0b4889360.PNG" rel=""><img alt="عمل الحاوية بنجاح.PNG" class="ipsImage ipsImage_thumbnailed" data-fileid="98879" data-unique="n9rgmlr3g" src="https://academy.hsoub.com/uploads/monthly_2022_05/6282a57a21757_.thumb.PNG.391288d6059ed0ba32a8b8eb8db8a642.PNG" style="width: 650px; height: auto;"></a>
</p>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<div class="ipsQuote_contents ipsClearfix">
		<p>
			ملاحظة: في حال العمل ضمن بروكسي أو عدة واجهات شبكة، فإن مرحلة تنزيل مثال hello-world ستفشل بالإضافة لظهور رسالة خطأ بعنوان 408، ومن الممكن أن يفشل التنزيل حتى في حال عدم العمل ضمن بروكسي قد تكون المشكلة في مزوّد خدمة الإنترنت، وأحد الحلول هو تبديل الشبكات للتمكن من تنزيل للمثال.
		</p>
	</div>
</blockquote>

<h2>
	إزالة دوكر من Fedora Linux
</h2>

<p>
	بغض النظر عن الطريقة المعتمدة لتثبيت دوكر، يمكن عن طريق الأمر التالي إلغاء تثبيت دوكر وجميع الحزم الخاصة به:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9642_14" style="">
<span class="pln">sudo dnf remove docker</span><span class="pun">-</span><span class="pln">ce docker</span><span class="pun">-</span><span class="pln">ce</span><span class="pun">-</span><span class="pln">cli containerd</span><span class="pun">.</span><span class="pln">io</span></pre>

<p>
	ولحذف الحاويات بشكل كلّي، يجب إزالة المجلدات التالية: var/lib/docker/ و var/lib/containerd/ ويتم ذلك باستخدام الأمر:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9642_16" style="">
<span class="pln">sudo rm </span><span class="pun">-</span><span class="pln">rf </span><span class="pun">/</span><span class="kwd">var</span><span class="pun">/</span><span class="pln">lib</span><span class="pun">/</span><span class="pln">dockersudo rm </span><span class="pun">-</span><span class="pln">rf </span><span class="pun">/</span><span class="kwd">var</span><span class="pun">/</span><span class="pln">lib</span><span class="pun">/</span><span class="pln">containerd</span></pre>

<p>
	وهكذا يكون قد أزيل دوكر بالكامل من فيدورا.
</p>

<p>
	ترجمة -وبتصرف- للمقال <a href="https://itsfoss.com/install-docker-fedora" rel="external nofollow">How to Install دوكر in Fedora</a> لصاحبه Pranav Krishna.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D8%AF%D9%88%D9%83%D8%B1-%D9%88%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85%D9%87-%D8%B9%D9%84%D9%89-%D8%AF%D8%A8%D9%8A%D8%A7%D9%86-r465/" rel="">كيفية تثبيت دوكر واستخدامه على دبيان</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-docker-compose-%D8%B9%D9%84%D9%89-%D8%AF%D8%A8%D9%8A%D8%A7%D9%86-r464/" rel="">كيفية تثبيت Docker Compose على دبيان</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">611</guid><pubDate>Mon, 16 May 2022 20:44:32 +0000</pubDate></item><item><title>&#x645;&#x62F;&#x62E;&#x644; &#x625;&#x644;&#x649; &#x62F;&#x648;&#x643;&#x631; Docker</title><link>https://academy.hsoub.com/devops/cloud-computing/docker/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%AF%D9%88%D9%83%D8%B1-docker-r607/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_04/62639ecf5f533_---Docker-.png.88af9c7c0c7f9b83a0d0d7bf5c52d083.png" /></p>

<p>
	دوكر Docker هو إطار عملٍ برمجي، يهدف إلى بناء وتشغيل وإدارة الحاويات على الخوادم والسحابة؛ وهو جزءٌ من مشروع <a href="http://mobyproject.org/" rel="external nofollow">Moby</a>؛ ويشير عادةً المصطلح "دوكر" إلى الأدوات من أوامرٍ وبرامج خفية، أو إلى الملفات من النوع دوكر Dockerfile.
</p>

<p>
	إذا أردت تشغيل تطبيق ويب، جرت العادة بأن تشتري خادمًا وتنزّل عليه نظام تشغيل <a href="https://academy.hsoub.com/devops/linux/%D9%85%D8%A7-%D9%87%D9%88-%D9%86%D8%B8%D8%A7%D9%85-%D8%A7%D9%84%D8%AA%D8%B4%D8%BA%D9%8A%D9%84-%D9%84%D9%8A%D9%86%D9%83%D8%B3%D8%9F-r451/" rel="">لينكس</a>، وتبدأ بإعداد حزمة LAMP، ومن ثمّ تشغِّل التطبيق؛ وعند زيادة الطلب على تطبيقك، ستلجأ إلى موازنة الحمل من خلال إعداد خادمٍ ثانٍ لضمان عدم توقّف التطبيق بسبب كثرة الزيارات.
</p>

<p>
	أمّا في وقتنا هذا اختلف الأمر؛ فبدلًا من الاعتماد على استخدام خادماتٍ وحيدة، بُنيت شبكة الإنترنت مثلًا اعتمادًا على مصفوفةٍ ضخمة من <a href="https://academy.hsoub.com/devops/servers/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%AE%D8%A7%D8%AF%D9%85-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-r574/?msclkid=798f8c4fc2d011ecbdb8c8650ede1e59" rel="">الخوادم</a> المترابطة ضمن نظامٍ اسمه الشائع هو "السحابة"، وبالتالي وبفضل الابتكارات، مثل نواة نطاق الأسماء و cgroups من لينكس، تحرّر مفهوم الخادم من قيود كونه عتادًا وأصبح عوضًا عن ذلك مفهومًا برمجيًا.
</p>

<p>
	يُطلق على الخوادم المبنية برمجيًا اسم <a href="https://academy.hsoub.com/devops/cloud-computing/%D8%A3%D8%A8%D8%B1%D8%B2-%D8%A7%D9%84%D9%85%D9%81%D8%A7%D9%87%D9%8A%D9%85-%D8%A7%D9%84%D8%AA%D9%8A-%D9%8A%D8%AC%D8%A8-%D8%B9%D9%84%D9%8A%D9%83-%D8%A7%D9%84%D8%A5%D9%84%D9%85%D8%A7%D9%85-%D8%A8%D9%87%D8%A7-%D8%B9%D9%86-%D8%A7%D9%84%D8%AD%D8%A7%D9%88%D9%8A%D8%A7%D8%AA-r561/" rel="">الحاويات</a> Containers؛ وهي مزيجٌ هجين من <a href="https://academy.hsoub.com/files/24-%D8%A3%D9%86%D8%B8%D9%85%D8%A9-%D8%A7%D9%84%D8%AA%D8%B4%D8%BA%D9%8A%D9%84-%D9%84%D9%84%D9%85%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D9%86/" rel="">نظام التشغيل</a> لينكس الذي يشغّلها، إضافةً إلى بيئة وقت تشغيل تفاعلية متوضعّة ضمن الخادم تمثّل محتويات الحاوية.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="96983" href="https://academy.hsoub.com/uploads/monthly_2022_04/cameron-venti-1cqicrwfqbi-unsplash.jpg.a57a6b9ab07bf30f495f638f8eb4f9af.jpg" rel=""><img alt="cameron-venti-1cqicrwfqbi-unsplash.jpg" class="ipsImage ipsImage_thumbnailed" data-fileid="96983" data-unique="2rzfmyakg" src="https://academy.hsoub.com/uploads/monthly_2022_04/cameron-venti-1cqicrwfqbi-unsplash.jpg.a57a6b9ab07bf30f495f638f8eb4f9af.jpg"></a>
</p>

<h2>
	التعرف على مفهوم الحاويات
</h2>

<p>
	يمكن تصنيف تقنية الحاويات إلى ثلاثة جوانب مختلفة:
</p>

<ul>
<li>
		البناء: إذ تُستخدم أداةٌ أو مجموعةٌ من الأدوات لبناء الحاوية، مثل أداة <a href="https://linuxcontainers.org/distrobuilder/introduction/" rel="external nofollow">distrobuilder</a> <a href="https://academy.hsoub.com/devops/servers/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%AD%D8%A7%D9%88%D9%8A%D8%A7%D8%AA-%D9%84%D9%8A%D9%86%D9%83%D8%B3-lxc-r219/?msclkid=32e9d3dbc2d111eca69409951dc9c0a4" rel="">لحاويات LXC</a>؛ وهي إحدى حاويات نظام لينكس وتمثّل بيئةً وهميةً تعمل على مستوى النظام تشغِّل عدة أنظمة لينكس معزولة على جهازٍ واحدٍ ذي نظام لينكس مضيف، وأداة Dockerfile لحاويات دوكر.
	</li>
	<li>
		التشغيل (المحرّك): إذ يُستخدم تطبيقٌ لتشغيل الحاوية، ويمثّل هذا التطبيق بالنسبة <a href="https://academy.hsoub.com/programming/php/%D8%AD%D8%A7%D9%88%D9%8A%D8%A9-%D8%AF%D9%88%D9%83%D8%B1-docker-%D9%88%D9%85%D8%AE%D8%B2%D9%86-apcu-%D9%81%D9%8A-php-r1200/?msclkid=c4147983c2d011ecb6470fbae6731f36" rel="">لحاويات دوكر</a> واجهة أسطر الأوامر docker command وتطبيق دوكر الخفي dockerd daemon؛ أمّا بالنسبة للحاويات الأُخرى فهو يشير غالبًا إلى التطبيق الخفي وأوامره، مثل podman (وهي أداةٌ مفتوحة المصدر مصمّمةٌ لتسهيل العثور على التطبيقات وتشغيلها وبنائها ومشاركتها ونشرها باستخدام الحاويات المفتوحة، كما توفّر واجهة سطر أوامر مألوفةٍ لأي شخص استخدم محرّك حاويات دوكر).
	</li>
	<li>
		التناسق Orchestration: وهي التقنية المستخدمة لإدارة مجموعةٍ من الحاويات وتتضمّن أنظمة تناسق الحاويات، مثل "Kubernetes" و "OKD".
	</li>
</ul>
<p>
	توفّر الحاويات غالبًا كلًا من التطبيق والضبط اللازم لعملها، وبالتالي لن يضطر مدير النظام لقضاء وقتٍ طويل في الحصول على تطبيقٍ لتشغيل الحاوية كون هذا التطبيق موجودٌ ومثبّتٌ أصلًا. ويمثّل كل من <a href="http://hub.docker.com/" rel="external nofollow">Dockerhub</a> و <a href="http://quay.io/" rel="external nofollow">Quay.io</a> مستودعات توفّر صورًا لاستخدامها من قِبل محركات الحاويات.
</p>

<p>
	ومن أهم ميزات الحاويات هي ديناميكيتها العالية؛ فهي قادرةٌ على التوقّف عن العمل والاختفاء ومن ثمّ العودة بكل مرونة، وذلك حسب حالة الحمل وبما يضمن توازنه، وتكون عملية إعادة تشغيل الحاوية سريعة ومنخفضة المتطلبات سواءً كان توقّفها ناتجٌ عن عطلٍ ما، أو ببساطة بسبب عدم الحاجة إليها عند انخفاض مستوى الحركة على الخادم، فالحاويات مصمّمةٌ لتظهر وتختفي بكل سلاسة.
</p>

<p>
	نظرًا لهذه الميزات وبما أنّ الحاويات سريعة الاختفاء من ناحية، وقادرة على تشغيل العديد منها عند الحاجة من ناحية أُخرى، فمن المتوقّع ألّا يجري مراقبتها وإدارتها من قِبل إنسانٍ في الوقت الفعلي، بل يحدث ذلك تلقائيًا.
</p>

<h2>
	بدائل حاويات دوكر
</h2>

<p>
	سهّلت حاويات لينكس إحراز تقدمٍ هائلٍ في الحوسبة عالية التوفّر، إذ يوجد العديد من مجموعات الأدوات القادرة على مساعدتك في تشغيل الخدمات، أو نظام التشغيل كاملًا في الحاويات.
</p>

<p>
	تشجّع <a href="https://www.opencontainers.org/" rel="external nofollow">مبادرة الحاوية المفتوحة OCI</a> -وهي منظمة معايير صناعية- الابتكار بما يضمن تجنُّب مشكلة قفل العميل vendor lock-in، أي اضطرار العميل إلى الاستمرار في استخدام منتجٍ أو خدمة بغض النظر عن جودتها؛ حيث أصبح بإمكانك وبفضل هذه المبادرة اختيار مجموعة أدوات الحاويات سواءٌ كانت دوكر، <a href="https://cri-o.io/" rel="external nofollow">CRI-O</a>، <a href="http://podman.io/" rel="external nofollow">Podman</a>، <a href="https://linuxcontainers.org/" rel="external nofollow">LXC</a>، أو غيرها.
</p>

<h2>
	خدمات الحاويات
</h2>

<p>
	لقد صُمّمت الحاويات بحيث تتضاعف بسرعة عند الحاجة، سواءٌ كنت تشغّل عدّة خدماتٍ مختلفةٍ معًا، أو نسخًا عديدة من خدماتٍ قليلة؛ فإذا قررت تشغيل خدمات في حاوية، فستحتاج إلى برمجية لاستضافة وإدارة هذه الحاوية، وهو ما يُسمّى <a href="https://academy.hsoub.com/devops/deployment/%D9%85%D8%A7-%D8%A7%D9%84%D9%81%D8%B1%D9%82-%D8%A8%D9%8A%D9%86-%D8%A7%D9%84%D8%AA%D9%86%D8%B3%D9%8A%D9%82-%D9%88%D8%A7%D9%84%D8%A3%D8%AA%D9%85%D8%AA%D8%A9%D8%9F-r562/" rel="">بتنسيق الحاوية container orchestration</a>، فبالرغم من كون دوكر وغيره من محركات الحاويات، مثل "Podman" و "CRI-O" أدواتٍ جيدة لتعريف الحاويات وصورها، إلّا أنّ عملها ينحصر في إنشاء وتشغيل الحاوية نفسها وليس تنظيمها وإدارتها. وتوفّر بعض المشاريع، مثل <a href="https://academy.hsoub.com/devops/cloud-computing/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-kubernetes-r467/" rel="">Kubernetes</a> و <a href="http://okd.io/" rel="external nofollow">OKD</a> منظمات للحاويات لكل من دوكر و "Podman" و "CRI-O" وغيرها.
</p>

<p>
	قد ترغب عند استخدامك لأي من هذه المشاريع في الحصول على الدعم اللاحق عبر مشروعٍ مثل <a href="http://openshift.io/" rel="external nofollow">OpenShift</a> المُعتمد أصلًا على "OKD".
</p>

<h2>
	ما عليك معرفته حول الإصدار المشترك من دوكر
</h2>

<p>
	جُمِّعت المكونات مفتوحة المصدر من دوكر ضمن منتجٍ يُدعى الإصدار المشترك من دوكر -أو اختصارًا docker-ce-، الذي يتضمّن محرك دوكر ومجموعةً من أوامر الطرفية لمساعدة مدراء الأنظمة على إدارة جميع الحاويات قيد الاستخدام. يمكنك تثبيت مجموعة الأدوات هذه عبر البحث عن دوكر في مدير الحزم الموزعة distribution's package manager.
</p>

<h2>
	لماذا نستخدم دوكر ؟
</h2>

<p>
	تُعدّ إمكانية اختيار التقنية المستخدمة في إنجاز مهمّةٍ ما واحدةً من أهم ميزات المصادر المفتوحة. ويوفّر محرّك دوكر بيئة تجريب مفيدة للمطورين كونها بيئةً نظيفةً وخفيفة الحمولة لا تحتاج تنظيمًا معقدًا، إذ يُعد استخدام الإصدار المشترك من دوكر docker-ce أحد أفضل الطرق للبدء باستخدام الحاويات لا سيما في حال كونه متوفرًا على نظامك وكنت على درايةٍ بسلسلة أدوات دوكر.
</p>

<p>
	يُعد كلٌ من <a href="http://hub.docker.com/" rel="external nofollow">Dockerhub</a> و <a href="http://quay.io/" rel="external nofollow">Quay.io</a> مستودعاتٍ تقدم صورًا لمحرك الحاوية الذي اخترته، ويُفضَّل استخدام "Podman" إذا كان الإصدار المشترك من دوكر غير متاح أو غير مدعوم.
</p>

<p>
	لا تزال الجهود تُبذل لتمكين المصادر المفتوحة على نحوٍ أكبر، لذا يجب أن تتماشى مشاريعك المستقبلية باستخدام الحاويات مع المصادر والمعايير المفتوحة؛ فبالرغم من كون الاضافات الاحتكارية مغلقة المصدر قد تبدو جذابة إلا أنّها تفقد مرونتها من حيث إمكانية الاختيار، كون أدواتك محصورةً في منتجٍ واحد. باختصار، ستمنحك الحاويات الحرية، طالما كانت هي حرة أصلًا.
</p>

<p>
	ترجمة -وبتصرف- للمقال <a href="https://opensource.com/resources/what-docker" rel="external nofollow">?What is Docker</a> من موقع opensource.com.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D9%85%D9%82%D8%AF%D9%91%D9%85%D8%A9-%D8%B9%D9%86-%D8%A7%D9%84%D9%85%D9%8F%D9%83%D9%88%D9%91%D9%86%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D9%8F%D8%B4%D8%AA%D8%B1%D9%8E%D9%83%D8%A9-%D9%81%D9%8A-docker-r21/" rel="">مقدّمة عن المُكوّنات المُشترَكة في Docker</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D9%86%D8%B8%D8%B1%D8%A9-%D8%B9%D8%A7%D9%85%D9%91%D8%A9-%D8%B9%D9%84%D9%89-%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A7%D9%84%D8%AD%D8%A7%D9%88%D9%8A%D9%91%D8%A7%D8%AA-containerization-%D8%B9%D9%84%D9%89-docker-r23/" rel="">نظرة عامّة على إعداد الحاويّات containerization على Docker</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D8%AD%D8%A7%D9%88%D9%8A%D8%A7%D8%AA-docker-r310/" rel="">التعامل مع حاويات Docker</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">607</guid><pubDate>Sat, 23 Apr 2022 06:55:54 +0000</pubDate></item><item><title>&#x645;&#x627; &#x647;&#x64A; &#x62A;&#x642;&#x646;&#x64A;&#x629; Docker&#x61F;</title><link>https://academy.hsoub.com/devops/cloud-computing/docker/%D9%85%D8%A7-%D9%87%D9%8A-%D8%AA%D9%82%D9%86%D9%8A%D8%A9-docker%D8%9F-r639/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_08/62f017a551ae7_docker.png.d62471f56a6c7623637ad14cd25e8ffa.png" /></p>

<p>
	بغض النظر عن المجال الذي قد تعمل به  في البرمجة في الغالب ستكون قد سمعت عن أداة تدعى <a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%AF%D9%88%D9%83%D8%B1-docker-r607/" rel="">دوكر Docker</a>.
</p>

<p>
	هذا الفيديو سيكون عبارة عن مدخل لك حتى تتعلم دوكر Docker وكيف  يعمل، وما هي المشاكل التي يعالجها دوكر Docker أساسًا.
</p>

<p style="text-align: center;">
	<iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="" frameborder="0" height="480" title="ما هي تقنية Docker" width="853" src="https://www.youtube.com/embed/pAJTmbXCv4I"></iframe>
</p>

<p>
	يمكنك تعلم المزيد حول أداة دوكر، عبر <a href="https://academy.hsoub.com/devops/cloud-computing/docker/" rel="">قسم دوكر بأكاديمية حسوب</a>، كما يمكنك تعلم البرمجة من خلال <a href="https://academy.hsoub.com/" rel="">الدورات المقدمة من الأكاديمية</a> للتمكن من استخدام التقنية كما يجب.
</p>
]]></description><guid isPermaLink="false">639</guid><pubDate>Tue, 19 Apr 2022 15:00:00 +0000</pubDate></item><item><title>&#x645;&#x627; &#x647;&#x64A; &#x635;&#x648;&#x631;&#x629; &#x627;&#x644;&#x62D;&#x627;&#x648;&#x64A;&#x629; container image&#x61F;</title><link>https://academy.hsoub.com/devops/cloud-computing/docker/%D9%85%D8%A7-%D9%87%D9%8A-%D8%B5%D9%88%D8%B1%D8%A9-%D8%A7%D9%84%D8%AD%D8%A7%D9%88%D9%8A%D8%A9-container-image%D8%9F-r587/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_02/61fa6448b086e_---.png.f167edabfc6ff6d3ceaa241fb57a5037.png" /></p>

<p>
	أصبحت <a href="https://academy.hsoub.com/devops/cloud-computing/%D8%A3%D8%A8%D8%B1%D8%B2-%D8%A7%D9%84%D9%85%D9%81%D8%A7%D9%87%D9%8A%D9%85-%D8%A7%D9%84%D8%AA%D9%8A-%D9%8A%D8%AC%D8%A8-%D8%B9%D9%84%D9%8A%D9%83-%D8%A7%D9%84%D8%A5%D9%84%D9%85%D8%A7%D9%85-%D8%A8%D9%87%D8%A7-%D8%B9%D9%86-%D8%A7%D9%84%D8%AD%D8%A7%D9%88%D9%8A%D8%A7%D8%AA-r561/" rel="">الحاويات Containers</a> جزءًا رئيسيًا من عمليات تكنولوجيا المعلومات. تحتوي <strong>صورة الحاوية</strong> container image على تطبيق جاهز packaged application إضافةً إلى اعتمادياتها dependencies ومعلومات حول الخدمات التي تشغلها عند إقلاعها.
</p>

<p>
	يمكنك إنشاء صور الحاويات عن طريق تحديد مجموعة من الأوامر المصاغة بطريقة خاصة، إما بإيداعها في سجل أو كملف دوكر Dockerfile. على سبيل المثال: ينشئ ملف دوكر أدناه حاويةً تتضمن تطبيق ويب مبني <a href="https://wiki.hsoub.com/PHP" rel="external">بلغة PHP</a>:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_20_8" style="">
<span class="pln">FROM registry</span><span class="pun">.</span><span class="pln">access</span><span class="pun">.</span><span class="pln">redhat</span><span class="pun">.</span><span class="pln">com</span><span class="pun">/</span><span class="pln">ubi8</span><span class="pun">/</span><span class="pln">ubi</span><span class="pun">:</span><span class="lit">8.1</span><span class="pln">

RUN yum </span><span class="pun">--</span><span class="pln">disableplugin</span><span class="pun">=</span><span class="pln">subscription</span><span class="pun">-</span><span class="pln">manager </span><span class="pun">-</span><span class="pln">y </span><span class="kwd">module</span><span class="pln"> enable php</span><span class="pun">:</span><span class="lit">7.3</span><span class="pln"> \
  </span><span class="pun">&amp;&amp;</span><span class="pln"> yum </span><span class="pun">--</span><span class="pln">disableplugin</span><span class="pun">=</span><span class="pln">subscription</span><span class="pun">-</span><span class="pln">manager </span><span class="pun">-</span><span class="pln">y install httpd php \
  </span><span class="pun">&amp;&amp;</span><span class="pln"> yum </span><span class="pun">--</span><span class="pln">disableplugin</span><span class="pun">=</span><span class="pln">subscription</span><span class="pun">-</span><span class="pln">manager clean all

ADD index</span><span class="pun">.</span><span class="pln">php </span><span class="pun">/</span><span class="kwd">var</span><span class="pun">/</span><span class="pln">www</span><span class="pun">/</span><span class="pln">html

RUN sed </span><span class="pun">-</span><span class="pln">i </span><span class="str">'s/Listen 80/Listen 8080/'</span><span class="pln"> </span><span class="pun">/</span><span class="pln">etc</span><span class="pun">/</span><span class="pln">httpd</span><span class="pun">/</span><span class="pln">conf</span><span class="pun">/</span><span class="pln">httpd</span><span class="pun">.</span><span class="pln">conf \
  </span><span class="pun">&amp;&amp;</span><span class="pln"> sed </span><span class="pun">-</span><span class="pln">i </span><span class="str">'s/listen.acl_users = apache,nginx/listen.acl_users =/'</span><span class="pln"> </span><span class="pun">/</span><span class="pln">etc</span><span class="pun">/</span><span class="pln">php</span><span class="pun">-</span><span class="pln">fpm</span><span class="pun">.</span><span class="pln">d</span><span class="pun">/</span><span class="pln">www</span><span class="pun">.</span><span class="pln">conf \
  </span><span class="pun">&amp;&amp;</span><span class="pln"> mkdir </span><span class="pun">/</span><span class="pln">run</span><span class="pun">/</span><span class="pln">php</span><span class="pun">-</span><span class="pln">fpm \
  </span><span class="pun">&amp;&amp;</span><span class="pln"> chgrp </span><span class="pun">-</span><span class="pln">R </span><span class="lit">0</span><span class="pln"> </span><span class="pun">/</span><span class="kwd">var</span><span class="pun">/</span><span class="pln">log</span><span class="pun">/</span><span class="pln">httpd </span><span class="pun">/</span><span class="kwd">var</span><span class="pun">/</span><span class="pln">run</span><span class="pun">/</span><span class="pln">httpd </span><span class="pun">/</span><span class="pln">run</span><span class="pun">/</span><span class="pln">php</span><span class="pun">-</span><span class="pln">fpm \
  </span><span class="pun">&amp;&amp;</span><span class="pln"> chmod </span><span class="pun">-</span><span class="pln">R g</span><span class="pun">=</span><span class="pln">u </span><span class="pun">/</span><span class="kwd">var</span><span class="pun">/</span><span class="pln">log</span><span class="pun">/</span><span class="pln">httpd </span><span class="pun">/</span><span class="kwd">var</span><span class="pun">/</span><span class="pln">run</span><span class="pun">/</span><span class="pln">httpd </span><span class="pun">/</span><span class="pln">run</span><span class="pun">/</span><span class="pln">php</span><span class="pun">-</span><span class="pln">fpm

EXPOSE </span><span class="lit">8080</span><span class="pln">
USER </span><span class="lit">1001</span><span class="pln">
CMD php</span><span class="pun">-</span><span class="pln">fpm </span><span class="pun">&amp;</span><span class="pln"> httpd </span><span class="pun">-</span><span class="pln">D FOREGROUND</span></pre>

<p>
	يضيف كل أمر في هذا الملف <strong>طبقةً</strong> إلى صورة الحاوية، وتضيف كل طبقة الاختلافات عن الطبقات التي تسبقها فقط، ثم تتكدس هذه الطبقات معًا لتشكل صورة حاوية قابلة للقراءة فقط.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="91257" href="https://academy.hsoub.com/uploads/monthly_2022_02/001bus-containers.png.ec76650031284bbab0d547807060b399.png" rel=""><img alt="001bus-containers.png" class="ipsImage ipsImage_thumbnailed" data-fileid="91257" data-unique="xl4nuy4xp" src="https://academy.hsoub.com/uploads/monthly_2022_02/001bus-containers.png.ec76650031284bbab0d547807060b399.png"></a>
</p>

<h2>
	آلية تنفيذ تلك الخطوات
</h2>

<p>
	عليك أن تتعلم بعض المفاهيم عن صورة الحاوية، ومن الضروري أن تفهمها وفق الترتيب التالي:
</p>

<ol>
<li>
		أنظمة الملفات الموحدة UnionFS.
	</li>
	<li>
		النسخ عند الكتابة
	</li>
	<li>
		أنظمة ملفات أوفرلاي Overlay
	</li>
	<li>
		اللاقطات Snapshotters
	</li>
</ol>
<h3>
	أنظمة ملفات يونيون Aufs
</h3>

<p>
	يكون نظام ملفات يونيون UnionFS مدمج في نواة نظام لينوكس. يسمح بدمج محتويات نظام ملفات مع محتويات نظام ملفات آخر مع إبقاء محتواهما "الفيزيائي" منفصلًا، لينتج نظام ملفات موحَّد رغم أن البيانات منظَّمة فعليًا في فروع.
</p>

<p>
	إن الغاية منها هي في حال كان لديك عدة صور تحتوي على بعض البيانات المتطابقة، تشارَك هذه البيانات بين الصور باستخدام ما يسمى <em>الطبقة</em>، بدل أن تنسَخ عدة مرات.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="91258" href="https://academy.hsoub.com/uploads/monthly_2022_02/002unionfs.png.199c1b5a089acbc7793bfda1fd2973db.png" rel=""><img alt="002unionfs.png" class="ipsImage ipsImage_thumbnailed" data-fileid="91258" data-unique="i2bwz96rn" src="https://academy.hsoub.com/uploads/monthly_2022_02/002unionfs.png.199c1b5a089acbc7793bfda1fd2973db.png" style="width: 350px; height: auto;"></a>
</p>

<p style="text-align: center;">
	نظام ملفات يونيون
</p>

<p>
	تكون كل طبقة عبارة عن نظام ملفات يمكن مشاركته بين حاويات متعددة، فمثلًا الطبقة الأساسية base layer <a href="https://academy.hsoub.com/devops/servers/web/apache/%D8%AA%D9%86%D8%B5%D9%8A%D8%A8-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%AE%D8%A7%D8%AF%D9%88%D9%85-%D8%A3%D8%A8%D8%A7%D8%AA%D8%B4%D9%8A-httpd-%D8%B9%D9%84%D9%89-%D8%A3%D9%88%D8%A8%D9%86%D8%AA%D9%88-r190/" rel="">لخدمة httpd</a> هي الصورة الرسمية ل<a href="https://academy.hsoub.com/devops/servers/web/apache/%D8%AA%D9%86%D8%B5%D9%8A%D8%A8-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%AE%D8%A7%D8%AF%D9%88%D9%85-%D8%A3%D8%A8%D8%A7%D8%AA%D8%B4%D9%8A-httpd-%D8%B9%D9%84%D9%89-%D8%A3%D9%88%D8%A8%D9%86%D8%AA%D9%88-r190/" rel="">خادم الويب أباتشي Apache</a> ويمكن لأي عدد من الحاويات أن تستخدمها. سيوفر ذلك كثيرًا من مساحة القرص الصلب لأننا نستخدم نفس الطبقة الأساسية في جميع الحاويات.
</p>

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

<h3>
	النسخ عند الكتابة
</h3>

<p>
	عندما تشغل حاويةً يبدو لك بأن للحاوية نظام ملفات كامل خاص بها، أي أن كل حاوية تشغلها على <a href="https://academy.hsoub.com/files/24-%D8%A3%D9%86%D8%B8%D9%85%D8%A9-%D8%A7%D9%84%D8%AA%D8%B4%D8%BA%D9%8A%D9%84-%D9%84%D9%84%D9%85%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D9%86/" rel="">نظام التشغيل</a> يخصَّص لها نسخة من نظام الملفات. لكن ألن يستهلك ذلك مساحةً كبيرةً من القرص الصلب ويطيل مدة إقلاع الحاوية؟ لا، وذلك لعدم احتياج كل حاوية إلى نسخة نظام ملفات خاصة بها!
</p>

<p>
	تعمل الحاويات والصور بآلية النسخ عند الكتابة لتحقيق ذلك، فبدلًا من نسخ الملفات تشارك استراتيجية النسخ عند الكتابة Copy-on-write نسخة البيانات ذاتها مع عدة عمليات، ولا تنسخها إلا عندما تحتاج إحدى هذه العمليات إلى تعديل أو كتابة البيانات. قبل أن تنفَّذ أية عملية كتابة ضمن حاوية قيد التشغيل، توضع نسخة من الملف الذي سيعدَّل عليه على الطبقة القابلة للكتابة في هذه الحاوية. هنا تجري عملية <em>الكتابة</em> ولهذا تسمى بآلية <em>النسخ عند الكتابة</em>.
</p>

<p>
	تحقق هذه الاستراتيجية أقصى استفادة من مساحة القرص الصلب ومن أداء مدة إقلاع الحاوية، وتعمل بالتزامن مع نظام ملفات يونيون.
</p>

<h3>
	أنظمة ملفات أوفرلاي Overlay
</h3>

<p>
	يتوضّع نظام ملفات أوفرلاي فوق نظام ملفات موجود مسبقًا، ويجمع بين شجرة المجلدات الأعلى والأدنى، ويقدمها بأنها مجلد واحد، وتسمى هذه المجلدات <em>طبقات</em>. تبقى الطبقة الأدنى دون تعديل، ولا تضيف كل طبقة إلا الاختلافات (يطلق عليها في مصطلحات الحوسبة اختصارًا diff) عن الطبقة الأدنى منها، ويشار إلى عملية التوحيد هذه <strong>بالوصل الموحد</strong> union mount.
</p>

<p>
	يطلَق على أدنى مجلد أو طبقة الصورة lowerdir، أما المجلد الأعلى يدعى upperdir. أما آخر طبقة مركبة overlayed أو موحدة unified تدعى مدمَجة merged.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="91259" href="https://academy.hsoub.com/uploads/monthly_2022_02/003rect1036.png.30c5a57b4d3d37f71e18b744a1dba170.png" rel=""><img alt="003rect1036.png" class="ipsImage ipsImage_thumbnailed" data-fileid="91259" data-unique="nt93fd2qv" src="https://academy.hsoub.com/uploads/monthly_2022_02/003rect1036.png.30c5a57b4d3d37f71e18b744a1dba170.png" style="width: 350px; height: auto;"></a>
</p>

<p style="text-align: center;">
	نظام الملفات متعدد الطبقات
</p>

<p>
	تتضمن المصطلحات الموحّدة تعاريف الطبقات التالية:
</p>

<ul>
<li>
		الطبقة الأساسية Base layer: توجد فيها ملفات نظام ملفاتك، وبالنسبة لصور الحاويات ستكون هذه الطبقة الصورة الأساسية لديك base image.
	</li>
	<li>
		الطبقة المركّبة Overlay layer: تدعى غالبًا <em>طبقة الحاوية</em> لأن كل التغييرات التي تجري على الحاوية قيد التشغيل مثل إضافة ملفات أو حذفها أو تعديلها تكتَب على هذه الطبقة القابلة للكتابة. تخزن كافة التغييرات التي أجريت على هذه الطبقة على الطبقة التي تليها، وهي المنظور <strong>الموحَّد</strong> للطبقة الأساسية وطبقة الاختلافات.
	</li>
	<li>
		طبقة الاختلافات Diff layer: تحتوي على كافة التغييرات التي أجريت في الطبقة المركبة. فإذا كتبت شيئًا موجودًا مسبقًا في الطبقة الأساسية ينسخ نظام ملفات أوفرلاي الملف إلى طبقة الاختلافات ويجري التعديلات التي أردت كتابتها، وهذا ما يدعى <strong>بالنسخ عند الكتابة</strong>.
	</li>
</ul>
<h2>
	اللاقطات
</h2>

<p>
	تستطيع الحاويات إجراء التغييرات وإدارتها وتعميمها بأنها جزء من نظام ملفاتها باستخدام الطبقات وبرامج المخططات graph drivers، لكن العمل على برامج المخططات معقد ويحتمَل ظهور أخطاء فيه. تختلف اللاقطات SnapShotters عن برامج المخططات لأنها لا تكون على دراية بالصور أو الحاويات.
</p>

<p>
	تشابه طريقة عمل اللاقطات طريقة عمل <a href="https://academy.hsoub.com/programming/workflow/git/%D9%81%D9%8A%D8%AF%D9%8A%D9%88-%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-git-r658/" rel="">Git</a> في نواحٍ مثل مفهوم الأشجار وتتبع التغيرات على الأشجار لكل إيداع commit. تمثل <em>اللقطة</em> snapshot حالة نظام الملفات. تتضمن اللاقطات علاقات من نوع أب-ابن باستخدام مجموعة من المجلدات. تتخذ <strong>الاختلافات</strong> diff مكانها بين أب ولقطته لإنشاء طبقة.
</p>

<p>
	توفر اللاقطات <a href="https://academy.hsoub.com/programming/general/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-api-r1314/" rel="">واجهة برمجية <abbr title="Application Programming Interface | واجهة برمجية">API</abbr></a> لتخصيص وتحميل وإجراء لقطة لأنظمة الملفات التجريدية متعددة الطبقات.
</p>

<h2>
	الخاتمة
</h2>

<p>
	تكونت لديك الآن فكرة جيدة عن ماهية صور الحاويات، وكيف يجعل نهجها متعدد الطبقات الحاويات قابلة للنقل.
</p>

<p>
	ترجمة -وبتصرف- للمقال <a href="https://opensource.com/article/21/8/container-image" rel="external nofollow">?What is a container image</a> لصاحبه Nived V
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-docker-r3/" rel="">تعرف على Docker</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D9%86%D8%B8%D8%B1%D8%A9-%D8%B9%D8%A7%D9%85%D9%91%D8%A9-%D8%B9%D9%84%D9%89-%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A7%D9%84%D8%AD%D8%A7%D9%88%D9%8A%D9%91%D8%A7%D8%AA-containerization-%D8%B9%D9%84%D9%89-docker-r23/" rel="">نظرة عامّة على إعداد الحاويّات containerization على Docker</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D8%AD%D8%A7%D9%88%D9%8A%D8%A7%D8%AA-docker-r310/" rel="">التعامل مع حاويات Docker</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">587</guid><pubDate>Wed, 16 Feb 2022 10:58:22 +0000</pubDate></item></channel></rss>
