إعداد VPS الذي يعمل فعلا: Node.js و PM2 و Nginx ونشر بدون توقف
إعداد نشر VPS الدقيق الذي أستخدمه في بيئة الإنتاج — تقوية Ubuntu، وضع المجموعات في PM2، وكيل عكسي Nginx، SSL، وسكريبت نشر لم يخذلني بعد. لا نظريات، فقط ما يعمل.
هذه المدونة تعمل على VPS بـ 10 دولارات/شهر. ليس Vercel، ليس AWS، ليس مجموعة Kubernetes يديرها فريق من ستة أشخاص. صندوق Ubuntu واحد مع Nginx و PM2 وسكريبت bash ينشر في أقل من 30 ثانية.
جربت المسارات الأخرى. استخدمت Vercel (رائع حتى تحتاج وظائف مجدولة أو WebSockets مستمرة أو مجرد تحكم). استخدمت AWS (رائع إذا كنت تستمتع بقضاء نصف يومك في سياسات IAM). دائما أعود إلى VPS.
لكن إليك المشكلة: كل دليل "انشر على VPS" على الإنترنت يتوقف عند المسار السعيد. يُريك كيف تثبت Node.js وتشغل node server.js ويسميها إنتاج. ثم يتعرض خادمك لهجوم القوة الغاشمة على SSH، وتموت عمليتك في الساعة 3 صباحا لأن لا أحد أعد مدير عمليات، وشهادة SSL انتهت صلاحيتها منذ ثلاثة أشهر.
هذا هو الدليل الذي كنت أتمنى لو كان لدي. كل شيء هنا مُختبر في المعركة — هذا الإعداد بالذات يخدم الصفحة التي تقرأها الآن.
ابدأ بالأمان، وليس الكود#
قبل أن تفكر حتى في Node.js، أمّن الصندوق. مثيلات VPS الجديدة هي أهداف. الروبوتات الآلية تبدأ بضرب منفذ SSH في غضون دقائق من التوفير.
أنشئ مستخدما غير الجذر#
adduser deploy
usermod -aG sudo deployإعداد مصادقة مفتاح SSH#
على جهازك المحلي:
ssh-copy-id deploy@your-server-ipثم عطّل مصادقة كلمة المرور بالكامل:
sudo nano /etc/ssh/sshd_configPasswordAuthentication no
PermitRootLogin nosudo systemctl restart sshdإذا تخطيت هذا، سترى آلاف محاولات تسجيل الدخول الفاشلة في سجلات المصادقة في غضون أيام. هذه ليست جنون ارتياب — إنه يوم عادي على الإنترنت العام.
جدار الحماية مع UFW#
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enableهذا كل شيء. أربع قواعد. فقط حركة SSH والويب تمر.
Fail2Ban#
sudo apt install fail2ban -y
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.localعدّل /etc/fail2ban/jail.local:
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 3600
findtime = 600sudo systemctl enable fail2ban
sudo systemctl start fail2banثلاث محاولات SSH فاشلة وتُحظر لمدة ساعة. شاهدت Fail2Ban يحظر مئات عناوين IP في يوم واحد. إنه يعمل.
تحديثات الأمان التلقائية#
sudo apt install unattended-upgrades -y
sudo dpkg-reconfigure -plow unattended-upgradesخادمك الآن سيثبت تصحيحات الأمان تلقائيا. شيء أقل لتنساه.
Node.js: استخدم NVM، وليس apt#
أرى هذا في كل دليل: sudo apt install nodejs. لا تفعل ذلك.
مستودعات حزم Ubuntu تشحن إصدارات Node.js قديمة. حتى PPA من NodeSource يتأخر. وعندما تحتاج للتبديل بين Node 20 و Node 22 لمشاريع مختلفة، أنت عالق.
NVM يحل هذا:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
source ~/.bashrc
nvm install --lts
nvm alias default lts/*الآن تحقق:
node -v # v22.x.x أو أيا كان LTS الحالي
npm -vالنصيحة غير الواضحة: عندما تثبت حزما عامة مع NVM (مثل PM2)، تكون مرتبطة بذلك الإصدار من Node. إذا غيرت الإصدارات بـ nvm use، تختفي حزمك العامة. عيّن الافتراضي والتزم به على الخادم:
nvm alias default 22حدث هذا معي مرة واحدة بالضبط. مرة واحدة كانت كافية.
PM2: مدير العمليات الذي يستحق مكانه#
PM2 هو الفرق بين "منشور" و "جاهز للإنتاج". يتعامل مع إدارة العمليات والتجميع وتدوير السجلات وإعادة التشغيل التلقائي عند الأعطال وسكريبتات بدء التشغيل. مجانا.
التثبيت والإعداد#
npm install -g pm2إعداد النظام البيئي#
لا تبدأ التطبيقات بعلامات سطر الأوامر. استخدم ملف ecosystem.config.js. إنه خاضع لتحكم الإصدارات وقابل للتكرار وموثق ذاتيا.
// ecosystem.config.js
module.exports = {
apps: [
{
name: "akousa",
script: "node_modules/.bin/next",
args: "start -p 3002",
cwd: "/var/www/akousa.net",
instances: 2,
exec_mode: "cluster",
max_memory_restart: "500M",
env: {
NODE_ENV: "production",
PORT: 3002,
},
// إيقاف رشيق
kill_timeout: 5000,
listen_timeout: 10000,
wait_ready: false,
// التسجيل
log_date_format: "YYYY-MM-DD HH:mm:ss Z",
error_file: "/var/log/pm2/akousa-error.log",
out_file: "/var/log/pm2/akousa-out.log",
merge_logs: true,
// إعادة التشغيل التلقائي عند الفشل
autorestart: true,
max_restarts: 10,
min_uptime: "10s",
// لا تراقب في بيئة الإنتاج
watch: false,
},
],
};دعني أشرح الخيارات المهمة:
instances: 2 بدلا من "max": على VPS صغير بنواة واحدة أو اثنتين، "max" يبدو ذكيا لكنه سيولد عمليات تتصارع على الموارد أثناء البناء. مثيلان يعطيانك إعادة تحميل بدون توقف مع ترك هامش. على آلة بـ 4+ أنوية، بالتأكيد استخدم "max".
exec_mode: "cluster": هذا ما يُمكّن إعادة التحميل بدون توقف. بدون وضع المجموعات، pm2 reload هو مجرد إعادة تشغيل أنيقة. مع وضع المجموعات، يُعيد PM2 تشغيل المثيلات واحدة تلو الأخرى — تطبيقك لا يتوقف أبدا بالكامل.
max_memory_restart: "500M": تطبيق Next.js لديه تسرب ذاكرة؟ PM2 سيعيد تشغيله قبل أن يقتل الخادم بنفاد الذاكرة. أنقذني هذا من تنبيهات الساعة 2 صباحا أكثر من مرة.
kill_timeout: 5000: يعطي تطبيقك 5 ثوان لإنهاء الطلبات الجارية قبل أن يقتله PM2 بالقوة. الافتراضي (1600 ملي ثانية) عنيف جدا للتطبيقات ذات اتصالات قاعدة البيانات.
watch: false: رأيت أشخاصا يتركون watch: true في بيئة الإنتاج. PM2 حينها يُعيد تشغيل التطبيق كلما تغير ملف سجل. تطبيقك يدخل حلقة إعادة تشغيل. لا تفعل ذلك.
سكريبت بدء التشغيل#
اجعل PM2 ينجو من إعادة التشغيل:
pm2 startup systemd
# انسخ وشغّل الأمر الذي يُخرجه
pm2 saveهذا يولد خدمة systemd. بعد إعادة تشغيل الخادم، يعود تطبيقك تلقائيا. اختبره — أعد تشغيل خادمك وتحقق. لا تفترض.
تدوير السجلات#
السجلات ستأكل قرصك في النهاية. ثبّت وحدة التدوير:
pm2 install pm2-logrotate
pm2 set pm2-logrotate:max_size 50M
pm2 set pm2-logrotate:retain 7
pm2 set pm2-logrotate:compress true50 ميغابايت حد أقصى لكل ملف، احتفظ بـ 7 ملفات مُدوّرة، اضغط القديمة. بدون هذا، رأيت /var/log يملأ قرصا بـ 25 غيغابايت في ثلاثة أسابيع على تطبيق بحركة معتدلة.
Nginx: الوكيل العكسي الذي يفعل أكثر مما تظن#
"لماذا لا تكشف Node.js مباشرة على المنفذ 80؟"
لأن Nginx يتعامل مع أشياء لا يجب أن يهدر Node.js دوراته عليها: إنهاء SSL، تقديم الملفات الثابتة، ضغط gzip، تخزين الطلبات المؤقت، حدود الاتصالات، والتعامل الرشيق مع العملاء البطيئين. مكتوب بـ C ومصمم خصيصا لهذا.
التثبيت#
sudo apt install nginx -yالإعداد#
# /etc/nginx/sites-available/akousa.net
upstream node_app {
server 127.0.0.1:3002;
keepalive 64;
}
server {
listen 80;
listen [::]:80;
server_name akousa.net www.akousa.net;
# إعادة توجيه كل HTTP إلى HTTPS
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name akousa.net www.akousa.net;
# SSL (يُدار بواسطة Certbot — تُضاف هذه الأسطر تلقائيا)
ssl_certificate /etc/letsencrypt/live/akousa.net/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/akousa.net/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
# ترويسات الأمان
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
# ضغط Gzip
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_min_length 256;
gzip_types
text/plain
text/css
text/javascript
application/javascript
application/json
application/xml
image/svg+xml
application/wasm;
# إعدادات الوكيل
location / {
proxy_pass http://node_app;
proxy_http_version 1.1;
# الترويسات
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# دعم WebSocket (إذا احتجته يوما)
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# المهل الزمنية — سخية لكن ليست لا نهائية
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# التخزين المؤقت — دع Nginx يتعامل مع العملاء البطيئين
proxy_buffering on;
proxy_buffer_size 16k;
proxy_buffers 4 32k;
proxy_busy_buffers_size 64k;
}
# أصول Next.js الثابتة — دع Nginx يقدمها مباشرة
location /_next/static/ {
alias /var/www/akousa.net/.next/static/;
expires 365d;
access_log off;
add_header Cache-Control "public, immutable";
}
# الملفات الثابتة العامة
location /static/ {
alias /var/www/akousa.net/public/static/;
expires 30d;
access_log off;
}
# حظر الوصول إلى ملفات النقطة
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
}فعّله:
sudo ln -s /etc/nginx/sites-available/akousa.net /etc/nginx/sites-enabled/
sudo rm /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginxشغّل دائما nginx -t قبل إعادة التحميل. ذات مرة دفعت إعدادا معطلا وأوقفت الموقع لأنني تخطيت فحص الصياغة. الأحرف الخمسة nginx -t كانت ستوفر عليّ ثلاثين دقيقة من التصحيح المذعور.
أشياء تفوتها معظم الأدلة في هذا الإعداد:
كتلة upstream مع keepalive 64: Nginx يعيد استخدام الاتصالات بخلفية Node.js بدلا من فتح اتصال TCP جديد لكل طلب. هذا مهم تحت الحمل.
proxy_buffering on: Nginx يقرأ الاستجابة بالكامل من Node.js إلى الذاكرة، ثم يرسلها إلى العميل بأي سرعة يستطيع العميل استقبالها. بدون هذا، عميل بطيء على اتصال 3G يحجز عامل Node.js الخاص بك.
تقديم _next/static/ مباشرة: هذه أصول مُجزّأة وغير قابلة للتغيير. دع Nginx يقدمها من القرص بترويسة تخزين مؤقت لمدة 365 يوما. عمليات Node.js الخاصة بك لا يجب أن تهدر وقتها على هذا.
SSL في خمس دقائق#
Let's Encrypt حلت مشكلة SSL. إذا كنت لا تزال تدفع مقابل الشهادات في 2026، توقف.
sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d akousa.net -d www.akousa.netCertbot سيسألك عن بريدك الإلكتروني، يقبل شروط الخدمة، ويعدل إعداد Nginx تلقائيا ليتضمن توجيهات SSL. هذا كل شيء.
التحقق من التجديد التلقائي#
Certbot يثبت مؤقت systemd يتحقق مرتين يوميا ويجدد الشهادات في غضون 30 يوما من انتهاء الصلاحية:
sudo systemctl list-timers | grep certbotاختبر أن التجديد يعمل:
sudo certbot renew --dry-runإذا نجح الاختبار، لن تفكر في SSL مرة أخرى. إذا فشل، عادة لأن المنفذ 80 محظور (تحقق من قواعد UFW) أو Nginx لا يعمل.
شيء اكتشفته: إذا أعددت Nginx قبل تشغيل Certbot، تأكد أن كتلة الخادم تستمع على المنفذ 80 بدون إعادة توجيه HTTPS أولا. Certbot يحتاج الوصول إلى المنفذ 80 لتحدي HTTP-01. بعد تشغيل Certbot بنجاح، عندها أضف إعادة التوجيه.
سكريبت النشر#
هذا هو السكريبت الذي يعمل كل مرة أدفع فيها إلى بيئة الإنتاج. لا منصة CI/CD، لا GitHub Actions. فقط SSH و bash.
#!/bin/bash
# deploy.sh — نشر بدون توقف تقريبا
set -euo pipefail
APP_DIR="/var/www/akousa.net"
APP_NAME="akousa"
LOG_FILE="/var/log/deploy.log"
HEALTH_URL="http://localhost:3002"
MAX_RETRIES=10
RETRY_INTERVAL=3
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
log "=== Deploy started ==="
cd "$APP_DIR"
# سحب أحدث الكود
log "Pulling latest changes..."
git pull origin main 2>&1 | tee -a "$LOG_FILE"
# تثبيت التبعيات
log "Installing dependencies..."
npm install --legacy-peer-deps 2>&1 | tee -a "$LOG_FILE"
# البناء
log "Building application..."
rm -rf .next
npm run build 2>&1 | tee -a "$LOG_FILE"
if [ $? -ne 0 ]; then
log "ERROR: Build failed. Aborting deploy."
exit 1
fi
# إعادة تحميل PM2 (بدون توقف في وضع المجموعات)
log "Reloading PM2..."
pm2 reload "$APP_NAME" 2>&1 | tee -a "$LOG_FILE"
pm2 save 2>&1 | tee -a "$LOG_FILE"
# فحص الصحة مع إعادة المحاولة
log "Running health check..."
for i in $(seq 1 $MAX_RETRIES); do
HTTP_CODE=$(curl -s -o /dev/null -w '%{http_code}' "$HEALTH_URL" 2>/dev/null || echo "000")
if [ "$HTTP_CODE" = "200" ]; then
log "Health check passed (HTTP $HTTP_CODE)"
log "=== Deploy completed successfully ==="
exit 0
fi
log "Health check attempt $i/$MAX_RETRIES (HTTP $HTTP_CODE). Retrying in ${RETRY_INTERVAL}s..."
sleep $RETRY_INTERVAL
done
log "ERROR: Health check failed after $MAX_RETRIES attempts"
log "Rolling back to previous PM2 state..."
pm2 restart "$APP_NAME" 2>&1 | tee -a "$LOG_FILE"
exit 1اجعله قابلا للتنفيذ:
chmod +x deploy.shانشر من جهازك المحلي:
ssh root@your-server-ip "bash /var/www/akousa.net/deploy.sh"القرارات الرئيسية في هذا السكريبت:
set -euo pipefail: السكريبت يتوقف فورا عند أي خطأ. بدون هذا، npm install الفاشل يستمر بصمت إلى خطوة البناء، وتحصل على خطأ غامض يهدر 20 دقيقة لتصحيحه.
rm -rf .next قبل البناء: Next.js لديه ذاكرة مؤقتة للبناء تنتج أحيانا مخرجات قديمة. وقعت في هذا مرة — صفحة أظهرت محتوى قديما رغم تحديث الكود المصدري. حذف مجلد البناء يضيف ربما 15 ثانية للبناء لكنه يضمن مخرجات جديدة.
pm2 reload بدلا من pm2 restart: هذا هو جزء عدم التوقف. في وضع المجموعات، reload يقوم بإعادة تشغيل تدريجية — يرفع مثيلات جديدة بالكود المحدث، ينتظر حتى تكون جاهزة، ثم يوقف القديمة بلطف. في أي نقطة لا تكون صفر مثيلات تعمل.
فحص الصحة مع إعادة المحاولة: Next.js يستغرق بضع ثوان للإحماء بعد إعادة التشغيل. السكريبت ينتظر حتى 30 ثانية (10 محاولات × 3 ثوان)، يتحقق إذا كان التطبيق يستجيب بـ HTTP 200. إذا لم يفعل، شيء خاطئ وتحتاج أن تعرف فورا — ليس أن تكتشف من مستخدم.
التراجع عند الفشل: إذا فشل فحص الصحة بعد كل المحاولات، السكريبت يعيد تشغيل PM2 (الذي يحمل آخر حالة محفوظة). ليس تراجعا مثاليا، لكنه أفضل من ترك الخادم في حالة معطلة.
عندما تتعطل الأشياء في الساعة 2 صباحا#
إليك ما صححت أخطاءه فعلا على هذا الإعداد بالذات:
"الموقع معطل"#
أول الأوامر التي يجب تشغيلها:
pm2 status
pm2 logs akousa --lines 50
sudo systemctl status nginx
sudo tail -50 /var/log/nginx/error.logتسع مرات من عشر، pm2 logs يخبرك فورا بما حدث. متغير بيئة مفقود، أو اتصال قاعدة بيانات فاشل، أو رفض وعد غير معالج.
"الذاكرة تستمر في النمو"#
pm2 monitهذا يعطيك لوحة تحكم حية لوحدة المعالجة المركزية والذاكرة لكل عملية. إذا ارتفعت الذاكرة بثبات بدون أن تستقر، لديك تسرب. إعداد max_memory_restart في إعداد النظام البيئي هو شبكة أمانك — PM2 سيعيد تشغيل العملية قبل أن تسقط الخادم.
للتحقيق الأعمق:
pm2 describe akousaهذا يُظهر وقت التشغيل وعدد إعادات التشغيل ولقطات الذاكرة. إذا رأيت 47 إعادة تشغيل في آخر 24 ساعة، هذا دليلك.
"شهادة SSL انتهت صلاحيتها"#
sudo certbot certificatesيسرد جميع الشهادات مع تواريخ انتهائها. إذا فشل التجديد التلقائي:
sudo certbot renew --force-renewal
sudo systemctl reload nginx"مساحة القرص ممتلئة"#
df -h
du -sh /var/log/*
pm2 flushpm2 flush يمسح جميع ملفات سجلات PM2 فورا. إذا لم تُعِدّ تدوير السجلات (أخبرتك)، هذا حيث تشعر بالألم.
الأمر الذي أشغله كل صباح#
ssh deploy@akousa.net "pm2 status && df -h / && uptime"ثلاثة أشياء في سطر واحد: هل عملياتي تعمل، هل قرصي بخير، هل الخادم محمّل. يستغرق ثانيتين. يكتشف المشاكل قبل المستخدمين.
ما لن تخبرك به معظم الأدلة#
خطوة البناء هي أكبر نقطة ضعف لديك. على VPS بـ 1 غيغابايت ذاكرة عشوائية، npm run build لتطبيق Next.js يمكن أن يستهلك 800 ميغابايت+ من الذاكرة. إذا كان PM2 يشغل تطبيقك في مثيلين أثناء البناء، ستنفد الذاكرة. الحلول: استخدم ملف swap (على الأقل 2 غيغابايت)، أو أوقف التطبيق أثناء البناء وتقبل بضع ثوان من التوقف. أستخدم swap.
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab--legacy-peer-deps في أمر التثبيت هو رائحة كود، وليس حلا. أستخدمه لأن بعض الحزم في شجرة التبعيات لم تحدث نطاقات تبعيات النظراء. كل بضعة أشهر أحاول إزالته. يوما ما سيعمل. حتى ذلك الحين، أنشر.
اختبر سكريبت النشر من الصفر. انسخ مستودعك على خادم جديد وشغّل كل خطوة يدويا. عدد مشاكل "يعمل على جهازي" المختبئة في سكريبتات النشر محرج. وجدت ثلاث مشاكل في سكريبتي عندما فعلت هذا — حزم عامة مفقودة وصلاحيات ملفات خاطئة ومسار لم يكن موجودا إلا بسبب إعداد يدوي سابق.
ضع عنوان IP لخادمك في إعداد SSH. توقف عن كتابة عناوين IP:
# ~/.ssh/config
Host akousa
HostName 69.62.66.94
User deploy
IdentityFile ~/.ssh/id_ed25519الآن ssh akousa هو كل ما تحتاجه. الأشياء الصغيرة تتراكم.
القائمة الكاملة#
قبل أن تقول أنك انتهيت:
- مستخدم غير جذر مع صلاحية sudo
- مصادقة مفتاح SSH فقط، مصادقة كلمة المرور معطلة
- UFW مُفعّل مع المنافذ الضرورية فقط مفتوحة
- Fail2Ban يحمي SSH
- تحديثات الأمان التلقائية مُفعّلة
- Node.js مثبت عبر NVM
- PM2 يشغل تطبيقك في وضع المجموعات
- سكريبت بدء تشغيل PM2 مُهيّأ (ينجو من إعادة التشغيل)
- تدوير سجلات PM2 مثبت
- وكيل عكسي Nginx بترويسات صحيحة
- SSL عبر Let's Encrypt مع تجديد تلقائي
- سكريبت نشر مع فحوصات صحة
- ملف swap مُهيّأ (لهامش البناء)
- مُختبر: أعد تشغيل الخادم وتحقق أن كل شيء يعود
هذا البند الأخير هو الذي يتخطاه الناس. لا تكن ذلك الشخص. أعد تشغيل الخادم، انتظر 60 ثانية، وتحقق إذا كان تطبيقك يعمل. إذا لم يكن كذلك، سكريبتات بدء التشغيل مُهيّأة بشكل خاطئ وستكتشف ذلك في أسوأ وقت ممكن.
هل هذا "بمستوى المؤسسات"؟#
لا. وهذا هو بيت القصيد.
هذا الإعداد يخدم هذه المدونة بشكل موثوق بأقل من 10 دولارات/شهر. يُنشر في 30 ثانية بأمر واحد. أفهم كل جزء منه. عندما ينكسر شيء، أعرف بالضبط أين أبحث.
هل يمكنني استخدام Docker؟ بالتأكيد. هل يمكنني استخدام Kubernetes؟ تقنيا. هل يمكنني إعداد خط أنابيب CI/CD كامل مع بيئات اختبار ونشر تدريجي؟ بالتأكيد.
لكنني تعلمت أن أفضل بنية تحتية هي التي تفهمها فعلا، ويمكنك تصحيح أخطائها في الساعة 2 صباحا، ولا تكلف أكثر مما يكسبه المشروع. لموقع شخصي أو MVP لمنتج SaaS أو شركة ناشئة صغيرة — هذا هو ذلك الإعداد.
انشر أولا. وسّع عندما تحتاج. ودائما، دائما، اختبر سكريبت النشر على خادم جديد.