مشروع مصغر: بناء وتغليف تطبيق تفاعلي مع قاعدة بيانات داخل حاوية معزولة
ماذا سنبني اليوم؟ سنتعلم كيف نغلف تطبيق ويب تفاعلي بسيط باستخدام Flask وقاعدة بيانات SQLite داخل حاوية Docker، لضمان بيئة تشغيل معزولة ومتسقة.
الخطوة 1: إعداد تطبيق Flask وقاعدة البيانات
سنقوم بإنشاء تطبيق ويب بسيط باستخدام إطار عمل Flask في بايثون. سيتيح هذا التطبيق عرض قائمة من العناصر وإضافة عناصر جديدة إليها. سيتم تخزين البيانات في قاعدة بيانات SQLite، والتي تُعد مثالية للمشاريع الصغيرة أو للاستخدام داخل حاوية.
ملف app.py:
import sqlite3
from flask import Flask, request, g, redirect, url_for, render_template
app = Flask(__name__)
app.config['DATABASE'] = 'database.db' # تحديد اسم ملف قاعدة البيانات
# دالة لإنشاء اتصال بقاعدة البيانات
def get_db():
db = getattr(g, '_database', None)
if db is None:
db = g._database = sqlite3.connect(app.config['DATABASE'])
db.row_factory = sqlite3.Row # لجعل النتائج قابلة للوصول بالاسم
return db
# دالة لغلق اتصال قاعدة البيانات عند انتهاء الطلب
@app.teardown_appcontext
def close_connection(exception):
db = getattr(g, '_database', None)
if db is not None:
db.close()
# دالة لتهيئة قاعدة البيانات (إنشاء الجدول إن لم يكن موجوداً)
def init_db():
with app.app_context():
db = get_db()
with app.open_resource('schema.sql', mode='r') as f:
db.cursor().executescript(f.read())
db.commit()
# تهيئة قاعدة البيانات عند بدء التطبيق لأول مرة (يمكن استدعاؤها يدوياً أو عند بدء التشغيل)
# init_db() # يمكن تفعيل هذا السطر لتهيئة القاعدة تلقائياً عند التشغيل
# المسار الرئيسي: عرض قائمة العناصر
@app.route('/')
def index():
db = get_db()
cur = db.execute('SELECT id, name FROM items ORDER BY id DESC')
items = cur.fetchall()
return render_template('index.html', items=items)
# المسار لإضافة عنصر جديد
@app.route('/add', methods=['POST'])
def add_item():
name = request.form['name']
db = get_db()
db.execute('INSERT INTO items (name) VALUES (?)', (name,))
db.commit()
return redirect(url_for('index'))
if __name__ == '__main__':
# لضمان تهيئة قاعدة البيانات عند تشغيل التطبيق محلياً
with app.app_context():
init_db()
app.run(host='0.0.0.0', debug=True) # تشغيل التطبيق على جميع الواجهات
ملف schema.sql (لإنشاء جدول قاعدة البيانات):
DROP TABLE IF EXISTS items;
CREATE TABLE items (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL
);
ملف templates/index.html (قالب HTML):
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>تطبيق العناصر</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; background-color: #f4f4f4; }
.container { max-width: 600px; margin: auto; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
h1 { text-align: center; color: #333; }
form { display: flex; margin-bottom: 20px; }
form input[type="text"] { flex-grow: 1; padding: 10px; border: 1px solid #ddd; border-radius: 4px 0 0 4px; }
form button { padding: 10px 15px; background-color: #007bff; color: white; border: none; border-radius: 0 4px 4px 0; cursor: pointer; }
form button:hover { background-color: #0056b3; }
ul { list-style: none; padding: 0; }
li { background: #e9ecef; margin-bottom: 8px; padding: 10px; border-radius: 4px; display: flex; justify-content: space-between; align-items: center; }
</style>
</head>
<body>
<div class="container">
<h1>قائمة العناصر</h1>
<form action="/add" method="post">
<input type="text" name="name" placeholder="أدخل اسم العنصر الجديد" required>
<button type="submit">إضافة</button>
</form>
<h2>العناصر الموجودة:</h2>
<ul>
{% for item in items %}
<li>{{ item.name }}</li>
{% else %}
<li>لا توجد عناصر بعد.</li>
{% endfor %}
</ul>
</div>
</body>
</html>
ملف requirements.txt (تحديد تبعيات بايثون):
Flask==2.3.2
ملاحظة تقنية: استخدام
app.app_context()معinit_db()يضمن أن وظائف تهيئة قاعدة البيانات يمكنها الوصول إلى إعدادات التطبيق مثلapp.config['DATABASE']، وهو أمر ضروري عند تهيئة قاعدة البيانات خارج سياق طلب HTTP.
الخطوة 2: بناء صورة Docker للتطبيق
الآن بعد أن أصبح لدينا تطبيق Flask جاهزًا، سنقوم بإنشاء Dockerfile. هذا الملف يصف كيفية بناء صورة Docker التي ستحتوي على تطبيقنا وجميع تبعياته، بما في ذلك بيئة تشغيل بايثون.
ملف Dockerfile:
# استخدام صورة بايثون أساسية خفيفة الوزن
FROM python:3.9-slim-buster
# تعيين دليل العمل داخل الحاوية
WORKDIR /app
# نسخ ملف تبعيات بايثون وتثبيتها
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# نسخ جميع ملفات التطبيق إلى دليل العمل داخل الحاوية
COPY . .
# تعيين متغير البيئة لتعطيل وضع التصحيح في بيئة الإنتاج
ENV FLASK_APP=app.py
ENV FLASK_ENV=production
# كشف المنفذ الذي يستمع عليه تطبيق Flask
EXPOSE 5000
# الأمر الذي سيتم تنفيذه عند بدء تشغيل الحاوية
# تشغيل التطبيق باستخدام Gunicorn لتحسين الأداء في بيئة الإنتاج
# For simplicity, we'll stick to python app.py as per Flask app definition
CMD ["python", "app.py"]
ملاحظة تقنية: الأمر
--no-cache-dirيقلل من حجم الصورة النهائية عن طريق منع pip من تخزين حزم التثبيت مؤقتًا.WORKDIRيحدد الدليل الافتراضي لجميع الأوامر اللاحقة.
الخطوة 3: بناء وتشغيل الحاوية
بعد إعداد Dockerfile، يمكننا الآن بناء صورة Docker ومن ثم تشغيل حاوية منها. سنستخدم أوامر Docker القياسية لإنجاز ذلك.
سكربت build_and_run.sh (للبناء والتشغيل):
#!/bin/bash
echo "بناء صورة Docker لتطبيق Flask..."
# بناء صورة Docker وتسميتها 'my-flask-app'
docker build -t my-flask-app .
echo "تشغيل حاوية Docker..."
# تشغيل الحاوية وربط المنفذ 5000 من الحاوية بالمنفذ 5000 على الجهاز المضيف
# -d لتشغيل الحاوية في الخلفية (detached mode)
docker run -d -p 5000:5000 --name flask-db-app my-flask-app
echo "تم تشغيل التطبيق. يمكنك الوصول إليه على http://localhost:5000"
echo "لعرض سجلات الحاوية: docker logs flask-db-app"
echo "لإيقاف الحاوية: docker stop flask-db-app"
echo "لحذف الحاوية: docker rm flask-db-app"
ملاحظة تقنية: استخدام
-p 5000:5000ينشئ ربطًا بين المنفذ 5000 داخل الحاوية والمنفذ 5000 على جهازك المضيف، مما يتيح لك الوصول إلى التطبيق من متصفح الويب الخاص بك. الخيار-dيسمح بتشغيل الحاوية في الخلفية.
الكود النهائي الكامل
لإنشاء هذا المشروع، قم بإنشاء بنية المجلدات التالية:
my-flask-app/
├── app.py
├── schema.sql
├── requirements.txt
├── Dockerfile
├── build_and_run.sh
└── templates/
└── index.html
app.py:
import sqlite3
from flask import Flask, request, g, redirect, url_for, render_template
app = Flask(__name__)
app.config['DATABASE'] = 'database.db'
def get_db():
db = getattr(g, '_database', None)
if db is None:
db = g._database = sqlite3.connect(app.config['DATABASE'])
db.row_factory = sqlite3.Row
return db
@app.teardown_appcontext
def close_connection(exception):
db = getattr(g, '_database', None)
if db is not None:
db.close()
def init_db():
with app.app_context():
db = get_db()
with app.open_resource('schema.sql', mode='r') as f:
db.cursor().executescript(f.read())
db.commit()
@app.route('/')
def index():
db = get_db()
cur = db.execute('SELECT id, name FROM items ORDER BY id DESC')
items = cur.fetchall()
return render_template('index.html', items=items)
@app.route('/add', methods=['POST'])
def add_item():
name = request.form['name']
db = get_db()
db.execute('INSERT INTO items (name) VALUES (?)', (name,))
db.commit()
return redirect(url_for('index'))
if __name__ == '__main__':
with app.app_context():
init_db()
app.run(host='0.0.0.0', debug=True)
schema.sql:
DROP TABLE IF EXISTS items;
CREATE TABLE items (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL
);
templates/index.html:
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>تطبيق العناصر</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; background-color: #f4f4f4; }
.container { max-width: 600px; margin: auto; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
h1 { text-align: center; color: #333; }
form { display: flex; margin-bottom: 20px; }
form input[type="text"] { flex-grow: 1; padding: 10px; border: 1px solid #ddd; border-radius: 4px 0 0 4px; }
form button { padding: 10px 15px; background-color: #007bff; color: white; border: none; border-radius: 0 4px 4px 0; cursor: pointer; }
form button:hover { background-color: #0056b3; }
ul { list-style: none; padding: 0; }
li { background: #e9ecef; margin-bottom: 8px; padding: 10px; border-radius: 4px; display: flex; justify-content: space-between; align-items: center; }
</style>
</head>
<body>
<div class="container">
<h1>قائمة العناصر</h1>
<form action="/add" method="post">
<input type="text" name="name" placeholder="أدخل اسم العنصر الجديد" required>
<button type="submit">إضافة</button>
</form>
<h2>العناصر الموجودة:</h2>
<ul>
{% for item in items %}
<li>{{ item.name }}</li>
{% else %}
<li>لا توجد عناصر بعد.</li>
{% endfor %}
</ul>
</div>
</body>
</html>
requirements.txt:
Flask==2.3.2
Dockerfile:
FROM python:3.9-slim-buster
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
ENV FLASK_APP=app.py
ENV FLASK_ENV=production
EXPOSE 5000
CMD ["python", "app.py"]
build_and_run.sh:
#!/bin/bash
echo "بناء صورة Docker لتطبيق Flask..."
docker build -t my-flask-app .
echo "تشغيل حاوية Docker..."
docker run -d -p 5000:5000 --name flask-db-app my-flask-app
echo "تم تشغيل التطبيق. يمكنك الوصول إليه على http://localhost:5000"
echo "لعرض سجلات الحاوية: docker logs flask-db-app"
echo "لإيقاف الحاوية: docker stop flask-db-app"
echo "لحذف الحاوية: docker rm flask-db-app"
النتيجة المتوقعة
بعد تنفيذ السكربت build_and_run.sh، سيتم بناء صورة Docker لتطبيقك وتشغيل حاوية منها في الخلفية. يمكنك الوصول إلى التطبيق التفاعلي عبر متصفح الويب الخاص بك على العنوان http://localhost:5000.
ستشاهد صفحة ويب بسيطة تحتوي على عنوان "قائمة العناصر"، حقل لإضافة عناصر جديدة، وقائمة تعرض العناصر الحالية. عند إضافة عنصر جديد، سيتم تخزينه في قاعدة بيانات SQLite داخل الحاوية وعرضه على الفور في القائمة. ستظل البيانات موجودة طوال عمر الحاوية.
يمكنك استخدام الأوامر المذكورة في سكربت التشغيل (docker logs، docker stop، docker rm) لإدارة الحاوية بعد تشغيلها.