لماذا تستضيف FastAPI ذاتيًا على VPS؟
يستمد FastAPI كل أدائه من نموذج ASGI غير المتزامن: اتصالات WebSocket دائمة، وبث للاستجابات، واستدعاءات متزامنة لخدمات أخرى. وغالبًا ما تقطع منصات serverless الاتصالات الطويلة وتفوتر كل طلب، مما يضرّ بواجهة API عالية الإنتاجية أو تعمل في الوقت الفعلي. على VPS، تشغّل Uvicorn بإدارة Gunicorn بعدد workers يساوي عدد الأنوية، وتُبقي الاتصالات مفتوحة طالما لزم الأمر، وتضع Nginx في الواجهة لأجل TLS والتخزين المؤقت (buffering). كما تتحكم في مجمّع الاتصالات نحو PostgreSQL وفي ذاكرة Redis المؤقتة، وهما رافعتان أساسيتان عندما يرتفع الحِمل — أمّا المعدّل الذي يُتحمَّل فعليًا فيُقاس على حركتك أنت.
فوائد ملموسة لاستضافة FastAPI ذاتيًا
- WebSockets وServer-Sent Events دون قطع الاتصال أو timeout مفروض.
- ضبط دقيق لـworkers الخاصة بـUvicorn/Gunicorn وفق عدد أنوية VPS.
- لا بدء بارد (cold start): تبقى العملية قائمة بين طلب وآخر.
- وثائق Swagger وReDoc تُقدَّم داخليًا، متاحة أو محمية حسب رغبتك.
- تكامل مباشر مع مجمّع PostgreSQL غير متزامن (asyncpg) ومع Redis.
- تكلفة ثابتة بغض النظر عن عدد استدعاءات API، مثالية لخلفية (backend) إنتاجية.
المتطلبات المسبقة للعتاد والبرمجيات والإصدارات
واجهة FastAPI خفيفة: يكفي 1 vCPU و1 غيغابايت من ذاكرة RAM للبدء، لكن استهدف 2 vCPU و2 غيغابايت بمجرد إضافة قاعدة بيانات وذاكرة تخزين مؤقت على نفس VPS. للحِمل المستمر (أكثر من 100 طلب/ثانية)، اشترط 4 vCPU و4 غيغابايت على الأقل.
الإصدارات الموصى بها في 2025: Python 3.11+ (3.12 للأداء الأقصى)، FastAPI 0.115+ (دعم كامل لـPydantic v2 وتعليقات Python 3.10+)، Uvicorn 0.30+ (إصلاحات استقرار event loop). ثبِّت عبر Docker مع صورة python:3.12-slim، أو من المستودع الرسمي بـpip install fastapi[standard]>=0.115 uvicorn[standard]>=0.30 gunicorn>=22.0.
من جهة البنية التحتية: Docker Engine 24+، Docker Compose v2 (plugin متكامل، docker compose)، Nginx 1.24+ كبروكسي عكسي، ونطاق مُوجَّه نحو VPS للحصول على شهادة TLS. نظام Ubuntu 22.04/24.04 LTS هو الأساس الموصى به؛ Debian 12 يعمل أيضًا.
نشر FastAPI خطوة بخطوة
تهيئة VPS والتطبيق
اتصل عبر SSH، وثبِّت Docker، واستنسخ مستودعك، وأنشئ ملف
.envمن النموذج.env.exampleالمُرفق بمشروعك. يجب أن يحتوي هذا الملف على الحد الأدنى:DATABASE_URL=postgresql+asyncpg://user:password@db:5432/appdb REDIS_URL=redis://redis:6379/0 SECRET_KEY=changeme-in-production CORS_ORIGINS=["https://yourdomain.com"] ENVIRONMENT=productionتأكّد من أن نقطة الدخول تكشف فعلًا
app = FastAPI()وأن التطبيق يُحمِّل إعداداته من متغيرات البيئة (لا تُكتب أبدًا في الكود مباشرة).التعبئة في حاوية باستخدام Dockerfile متعدد المراحل
يفصل النهج متعدد المراحل مرحلة البناء (تصريف الاعتماديات، توليد wheels) عن صورة وقت التشغيل، مما يُقلّص الحجم النهائي بنسبة 60 إلى 80% ويُزيل أدوات التصريف من سطح الهجوم:
# ── Stage 1 : build ────────────────────────────────────── FROM python:3.12-slim AS builder WORKDIR /app COPY requirements.txt . RUN pip install --upgrade pip && \ pip wheel --no-cache-dir --wheel-dir /wheels -r requirements.txt # ── Stage 2 : runtime ──────────────────────────────────── FROM python:3.12-slim WORKDIR /app COPY --from=builder /wheels /wheels RUN pip install --no-cache-dir /wheels/* && rm -rf /wheels COPY . . EXPOSE 8000 CMD ["gunicorn", "main:app", "-k", "uvicorn.workers.UvicornWorker", "--bind", "0.0.0.0:8000", "--workers", "4", "--timeout", "120", "--keep-alive", "5"]أضف نقطة نهاية
/healthفي تطبيقك لفحوصات السلامة:@app.get("/health") async def health(): return {"status": "ok"}ضبط workers في Gunicorn بالشكل الأمثل
صيغة
(2 × الأنوية) + 1هي نقطة الانطلاق لـworkers الـCPU-bound. بما أن FastAPI هو ASGI/غير متزامن، يتعامل كل worker مع طلبات متعددة تزامنيًا عبر event loop — للأحمال الـI/O-bound النموذجية (استدعاءات قاعدة البيانات، طلبات HTTP الخارجية)، النطاقالأنوية + 1إلى2 × الأنويةأنسب.مرِّر القيمة عبر متغير بيئة لتعديلها دون إعادة بناء الصورة:
WEB_CONCURRENCY=4 # في .env أو docker-compose.ymlCMD ["gunicorn", "main:app", "-k", "uvicorn.workers.UvicornWorker", "--bind", "0.0.0.0:8000", "--workers", "${WEB_CONCURRENCY:-4}"]تحت الحِمل العالي، راقب استخدام CPU لكل worker (
docker stats) وعدد الطلبات المنتظرة في سجلات Gunicorn (--access-logfile -) قبل زيادة عدد الـworkers.تنسيق الخدمات مع Docker Compose
في
docker-compose.yml، عرِّف خدمةapiوخدمةdbبـPostgreSQL وخدمةredis. استخدمdepends_onمعcondition: service_healthyكي تنتظر الـAPI جاهزية قاعدة البيانات قبل أن تبدأ:services: api: build: . env_file: .env ports: ["8000:8000"] depends_on: db: condition: service_healthy redis: condition: service_started healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8000/health"] interval: 30s retries: 3 db: image: postgres:16-alpine environment: POSTGRES_USER: user POSTGRES_PASSWORD: password POSTGRES_DB: appdb volumes: ["pgdata:/var/lib/postgresql/data"] healthcheck: test: ["CMD", "pg_isready", "-U", "user"] interval: 10s retries: 5 redis: image: redis:7-alpine volumes: pgdata:وضع Nginx كبروكسي عكسي
اضبط كتلة خادم بـ
proxy_pass http://api:8000، ولأجل WebSockets أضف الترويسات الضرورية. عطِّل التخزين المؤقت (buffering) للبث عند الحاجة:server { listen 443 ssl; server_name api.yourdomain.com; location / { proxy_pass http://api:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_buffering off; } }التأمين بشهادة TLS
احصل على شهادة Let's Encrypt عبر Certbot أو حاوية companion، وافرض HTTPS وأعِد توجيه HTTP. وتذكّر تقييد الوصول إلى
/docsو/openapi.jsonفي الإنتاج إذا لم تكن الـAPI عامة.التشغيل والاختبار
ابدأ باستخدام
docker compose up -d، وتحقّق من السلامة عبر نقطة النهاية/healthالتي أضفتها، واختبر الوثائق التفاعلية. راقب سجلات Uvicorn لضبط عدد الـworkers تحت الحِمل الفعلي.
بالنسبة إلى المهام الطويلة (إرسال رسائل البريد الإلكتروني، معالجة الصور، الاستدعاءات الخارجية البطيئة)، لا تحجب حلقة الأحداث (event loop): انقلها إلى worker يعمل في الخلفية باستخدام BackgroundTasks للحالات البسيطة، أو إلى Celery/ARQ مع Redis للمعالجات الثقيلة. واجهة API تستجيب بسرعة وتفوّض العمل غير المتزامن تبقى سريعة الاستجابة حتى تحت التزامن العالي.
قاعدة البيانات والترحيل
لتطبيقات FastAPI في الإنتاج، يوفر asyncpg مدمجًا مع SQLAlchemy 2.x (الوضع غير المتزامن) أفضل توازن بين الأداء وقابلية الصيانة. اضبط مجمّع الاتصالات وفق الحِمل المتوقع:
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker
engine = create_async_engine(
DATABASE_URL,
pool_size=10, # اتصالات دائمة
max_overflow=20, # اتصالات إضافية عند الذروة
pool_timeout=30, # ثوانٍ قبل الخطأ
pool_recycle=1800, # إعادة تدوير بعد 30 دقيقة لتجنب timeouts PostgreSQL
echo=False,
)
AsyncSessionLocal = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)أدخِل الجلسة في مساراتك عبر تبعية FastAPI:
async def get_db():
async with AsyncSessionLocal() as session:
yield sessionلترحيل المخطط (schema)، Alembic هو الأداة المعيارية. هيِّئه في مشروعك (alembic init migrations) ثم اضبط alembic.ini للإشارة إلى DATABASE_URL. الأوامر الأساسية:
# توليد ترحيل من نماذج SQLAlchemy
alembic revision --autogenerate -m "add_users_table"
# تطبيق الترحيلات المعلّقة
alembic upgrade head
# التحقق من الحالة الحالية
alembic currentفي الإنتاج، شغِّل alembic upgrade head قبل تشغيل الـAPI — أضفها كأمر تهيئة في docker-compose.yml أو في سكريبت بدء منفصل. لا تُشغِّل الترحيلات أبدًا من نسخ متعددة في آنٍ واحد.
CI/CD والنشر الآلي
تكفي خطوط أنابيب GitHub Actions بسيطة لمعظم مشاريع FastAPI: بناء الصورة، ودفعها إلى سجل، ونشرها على VPS عبر SSH مع zero-downtime.
# .github/workflows/deploy.yml
name: Deploy FastAPI
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build & push Docker image
run: |
docker build -t ghcr.io/${{ github.repository }}:${{ github.sha }} .
echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin
docker push ghcr.io/${{ github.repository }}:${{ github.sha }}
- name: Deploy via SSH
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.VPS_HOST }}
username: deploy
key: ${{ secrets.VPS_SSH_KEY }}
script: |
cd /opt/myapp
export IMAGE_TAG=${{ github.sha }}
docker compose pull api
docker compose up -d --no-deps api
docker compose exec api alembic upgrade headخيار --no-deps على docker compose up يُعيد تشغيل حاوية api فحسب دون المساس بـdb أو redis، مما يضمن zero-downtime للخدمات المساندة. للنشرات الأكثر متانة مع rollback فوري، تَوِّج صورك بـSHA الـcommit واحتفظ بآخر 3 نسخ محليًا.
المراقبة والسجلات
يمكنك الوصول إلى سجلات Uvicorn مباشرةً عبر Docker:
# سجلات في الوقت الفعلي
docker compose logs -f api
# آخر 100 سطر
docker compose logs --tail=100 apiللسجلات المهيكلة بصيغة JSON (قابلة للفهرسة بمجمِّع مثل Loki أو Elasticsearch)، استبدل مُسجِّل Python القياسي بـloguru أو structlog:
from loguru import logger
import sys
logger.remove()
logger.add(sys.stdout, format="{time} {level} {message}", serialize=True)
@app.middleware("http")
async def log_requests(request, call_next):
logger.info("request", method=request.method, url=str(request.url))
response = await call_next(request)
logger.info("response", status=response.status_code)
return responseلكشف مقاييس Prometheus، أضف نقطة نهاية /metrics بـprometheus-fastapi-instrumentator:
from prometheus_fastapi_instrumentator import Instrumentator
Instrumentator().instrument(app).expose(app, endpoint="/metrics")ثم قيِّد /metrics على شبكتك الداخلية من Nginx (allow 127.0.0.1; deny all;) لتفادي كشف المقاييس علنًا.
استكشاف الأخطاء الشائعة وإصلاحها
Worker timed out (pid XXXXX) — أنهى Gunicorn worker لم يستجب ضمن مهلة --timeout. الأسباب الشائعة: مسار متزامن (def بدلًا من async def) يحجب event loop، أو عملية حجب (قراءة ملف، استدعاء شبكة) تُنفَّذ دون asyncio.run_in_executor. زيادة --timeout يُخفي المشكلة؛ الإصلاح الصحيح هو جعل المسار غير متزامن أو تفويض العمل الحاجب.
Address already in use (port 8000) — عملية Gunicorn سابقة لا تزال نشطة. في Docker: docker compose down ثم docker compose up -d يُجبر على إعادة إنشاء الحاوية ويُحرّر المنفذ. تحقق بـdocker ps -a أنه لا تعمل حاوية zombie.
502 Bad Gateway بعد نشر — أعاد Nginx توجيه الطلب إلى الـAPI أثناء إعادة تشغيلها. انتظر 10 إلى 15 ثانية حتى يؤكد فحص سلامة Docker أن الحاوية الجديدة healthy قبل اعتبار النشر مكتملًا. أضف proxy_read_timeout 60; في Nginx لاستيعاب عمليات البدء البطيئة.
CORS في الإنتاج: No 'Access-Control-Allow-Origin' header — يخدم FastAPI الأصول (origins) المُعلَن عنها في CORSMiddleware. تحقق أن CORS_ORIGINS في ملف .env يحتوي على رابط الواجهة الأمامية بالضبط (البروتوكول + النطاق + المنفذ إن لم يكن قياسيًا، دون شرطة مائلة في النهاية). قيمة ["*"] تعمل في التطوير لكن لا يجب إرسالها إلى الإنتاج أبدًا.
رفض اتصال قاعدة البيانات عند البدء — بدأت الـAPI قبل أن تصبح PostgreSQL جاهزة. depends_on مع condition: service_healthy في Docker Compose يحلّ هذا. في Kubernetes أو في نشرات بدون Compose، نفِّذ حلقة retry مع تراجع أسي (exponential backoff) في كود بدء التطبيق.
نشر FastAPI بنقرة واحدة من Marketplace ServOrbit
تقدّم Marketplace ServOrbit قالب FastAPI Stack جاهزًا: Python 3.12 وUvicorn وGunicorn وNginx وPostgreSQL 16 تُثبَّت تلقائيًا على VPS عند الطلب. لا جلسة SSH للإعداد الأساسي — تصل مباشرة إلى خطوة استنساخ مستودعك وتشغيل Uvicorn. صفحة marketplace تحتوي المواصفات والحالات ودليل الاستخدام الأول.