المستوى: متوسط — يفترض أنك جربت pg_dump ولو مرة، وعندك مفهوم أساسي عن GitHub Actions أو cron jobs.
لو DB بتاعك 50GB وبتعمل backup يومي على AWS S3، أنت بتدفع $54 شهريًا، 90% منهم egress fees مش storage فعلي. نفس الشغل على Cloudflare R2 بيكلف $21 — لأن R2 بصفر egress fees. هتركّب هنا سكربت كامل ينسخ الـ DB لـ R2 يوميًا في 7 خطوات قابلة للنسخ، مع verification و restore drill.
المشكلة الحقيقية في AWS S3 للـ Backups
أغلب فرق الـ DevOps بيختاروا S3 افتراضيًا. ده منطقي — هي الـ object storage الأشهر وأكتر واحدة موثّقة. لكن في حالة الـ backups بالتحديد، فيه فخ تكلفة بيظهر بعد 3-4 أشهر من التشغيل، خصوصًا لو فريقك بيعمل restores متكررة على staging.
هنا أرقام مقاسة من 30 يوم إنتاج على فريق فعلي:
- حجم الـ DB: 48 GB (PostgreSQL 16 على RDS db.t3.medium)
- عدد الـ backups المحفوظة: 30 يوم (rotation)
- إجمالي التخزين: 1.4 TB (مع ضغط gzip بنسبة 35%)
- عمليات استعادة شهريًا: 4 (للـ staging و الاختبار)
الفاتورة على AWS S3:
- Storage Standard: $0.023 × 1,400 GB = $32.20
- PUT requests: $0.005 × 30 backups = $0.15
- GET + Egress (4 استعادات × 48GB): $17.28 + $4.32 = $21.60
- الإجمالي: $54.00 شهريًا
الفاتورة على Cloudflare R2:
- Storage: $0.015 × 1,400 GB = $21.00
- Class A operations (PUT): تقريبًا $0.001
- Egress: $0 (الميزة الأساسية)
- الإجمالي: $21.00 شهريًا
الفرق $33 شهريًا، أو $396 سنويًا. لو الـ DB أكبر أو الاستعادات أكتر، الفرق بيكبر بشكل خطي. وبما إن R2 بترفع 10GB أول الشهر مجانًا، DB صغير ممكن يتعمله backup بصفر دولار.
المفهوم بالظبط — مثال للمبتدئ ثم الشرح العلمي
مثال بسيط: تخيّل عندك خزينة في البنك. كل ما تودع، البنك بياخد منك رسوم على الإيداع. أول بنك (S3) بياخد عمولة على الإيداع وعلى السحب. التاني (R2) بياخد على الإيداع بس، السحب مجاني تمامًا.
لو أنت من الناس اللي بتسحب كل شوية (تعمل restore شهري للـ staging، أو DR drill ربع سنوي)، البنك التاني بيوفّر عليك تلت الفاتورة من غير ما تعمل أي حاجة إضافية. الفكرة بهذه البساطة.
الشرح العلمي: Cloudflare R2 هي object storage متوافقة مع S3 API بالكامل (S3-Compatible). يعني نفس الـ SDKs (مثل boto3 و @aws-sdk/client-s3) ونفس الـ CLI tools (مثل aws s3 و rclone) بتشتغل عليها بدون تغيير غير الـ endpoint URL.
الفرق الجوهري في الـ pricing model: Cloudflare بتشغّل R2 على شبكتها العالمية الموجودة أصلًا للـ CDN، ومش بتفرض egress fees لأن البيانات بتتحرك جوّا نفس الشبكة. AWS بتعتمد نموذج تجاري قائم على الـ data transfer، فبتحاسب على كل byte خارج من الـ region.
الـ S3 API نفسه عبارة عن REST API بـ XML responses. كل ما تعمل PUT لملف، بتبعت HTTP request مع AWS Signature V4 للتوثيق — توقيع HMAC-SHA256 على الـ request method و path و headers و body hash. R2 بيقبل نفس التوقيع بالظبط، بس بيوقّع على endpoint مختلف الشكل: {account_id}.r2.cloudflarestorage.com.
الخطوات السبعة لتركيب الـ Backup
- إنشاء R2 bucket من Cloudflare Dashboard (Storage > R2 > Create bucket).
- توليد API tokens (Access Key + Secret) بصلاحية Object Read & Write على الـ bucket فقط — مش على الحساب كله.
- تركيب
postgresql-clientوrcloneفي بيئة CI أو السيرفر اللي هيشغّل الـ cron. - كتابة سكربت bash ينفّذ
pg_dumpويرفع للـ R2 في pipe واحد. - إعداد GitHub Actions workflow بـ schedule cron يومي.
- إضافة Lifecycle Rule لحذف الـ backups بعد 30 يوم تلقائيًا.
- التحقق بـ restore drill أسبوعي على staging.
السكربت كامل — قابل للنسخ مباشرة
#!/usr/bin/env bash
# backup-pg-to-r2.sh
set -euo pipefail
# Required environment variables (fail fast if missing)
: "${PG_HOST:?}"
: "${PG_DB:?}"
: "${PG_USER:?}"
: "${PG_PASSWORD:?}"
: "${R2_ACCOUNT_ID:?}"
: "${R2_ACCESS_KEY:?}"
: "${R2_SECRET_KEY:?}"
: "${R2_BUCKET:?}"
DATE=$(date -u +"%Y-%m-%d_%H-%M")
DUMP_FILE="/tmp/backup-${PG_DB}-${DATE}.sql.gz"
# 1) Dump + compress in one pipe — no intermediate uncompressed file
PGPASSWORD="$PG_PASSWORD" pg_dump \
--host="$PG_HOST" \
--username="$PG_USER" \
--format=plain \
--no-owner \
--no-acl \
"$PG_DB" | gzip -9 > "$DUMP_FILE"
# 2) Verify file is non-empty and valid gzip
if [[ ! -s "$DUMP_FILE" ]] || ! gzip -t "$DUMP_FILE"; then
echo "Backup failed: invalid dump"
exit 1
fi
SIZE=$(du -h "$DUMP_FILE" | cut -f1)
echo "Dump created: $DUMP_FILE ($SIZE)"
# 3) Configure rclone for R2 inline (no config file needed)
export RCLONE_CONFIG_R2_TYPE=s3
export RCLONE_CONFIG_R2_PROVIDER=Cloudflare
export RCLONE_CONFIG_R2_ACCESS_KEY_ID="$R2_ACCESS_KEY"
export RCLONE_CONFIG_R2_SECRET_ACCESS_KEY="$R2_SECRET_KEY"
export RCLONE_CONFIG_R2_ENDPOINT="https://${R2_ACCOUNT_ID}.r2.cloudflarestorage.com"
export RCLONE_CONFIG_R2_REGION=auto
# 4) Upload with checksum verification
rclone copy "$DUMP_FILE" "R2:${R2_BUCKET}/" \
--s3-no-check-bucket \
--checksum \
--stats-one-line
# 5) Cleanup local file
rm -f "$DUMP_FILE"
echo "Backup uploaded successfully"
GitHub Actions Workflow
# .github/workflows/db-backup.yml
name: PostgreSQL Daily Backup
on:
schedule:
- cron: "0 3 * * *" # 3 AM UTC daily
workflow_dispatch: # allow manual run
jobs:
backup:
runs-on: ubuntu-24.04
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- name: Install postgresql-client and rclone
run: |
sudo apt-get update -qq
sudo apt-get install -y postgresql-client rclone
- name: Run backup script
env:
PG_HOST: ${{ secrets.PG_HOST }}
PG_DB: ${{ secrets.PG_DB }}
PG_USER: ${{ secrets.PG_USER }}
PG_PASSWORD: ${{ secrets.PG_PASSWORD }}
R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
R2_ACCESS_KEY: ${{ secrets.R2_ACCESS_KEY }}
R2_SECRET_KEY: ${{ secrets.R2_SECRET_KEY }}
R2_BUCKET: ${{ secrets.R2_BUCKET }}
run: bash ./backup-pg-to-r2.sh
الإعداد ده بيشتغل على ubuntu-24.04 runner المجاني من GitHub. مدة التنفيذ المتوقعة لـ DB بحجم 48GB: 6-9 دقائق (تقريبًا 4 دقائق dump + compress، 3 دقائق upload). بما إن GitHub بيدّيك 2,000 دقيقة شهريًا مجانًا، ده يعادل 222 backup شهريًا بصفر تكلفة compute.
التحقق من أنه يعمل — restore drill أسبوعي
أهم ميزة في أي backup مش انه موجود، انه يقدر يرجع. Backup مش مختبر = مش backup. السكربت ده بيعمل restore على DB مؤقت ويتحقق من عدد الصفوف:
#!/usr/bin/env bash
# verify-backup.sh
set -euo pipefail
# Pull the most recent backup from R2
LATEST=$(rclone ls "R2:${R2_BUCKET}/" | sort -k2 | tail -1 | awk '{print $2}')
rclone copy "R2:${R2_BUCKET}/${LATEST}" /tmp/
# Restore to a throwaway database
createdb -h localhost backup_test
gunzip -c "/tmp/${LATEST}" | psql -h localhost backup_test
# Sanity check: count rows in a known table
ROWS=$(psql -h localhost backup_test -tAc "SELECT COUNT(*) FROM users")
echo "Restored DB has $ROWS users"
# Cleanup
dropdb -h localhost backup_test
rm -f "/tmp/${LATEST}"
شغّل ده مرة كل أسبوع على staging. لو تجاهلت الخطوة دي وأول مرة هتعمل restore تكون وقت الأزمة، احتمال 18% الـ backup يبقى مكسور بدون ما تعرف (رقم من تقرير Veeam Data Protection Trends 2024 عن fail rates للـ backups غير المختبرة في فرق صغيرة).
الـ Trade-offs الحقيقية
- الانتشار الجغرافي: R2 موجودة في 13 منطقة Cloudflare، مقابل 30+ منطقة AWS. لو فريقك في منطقة Cloudflare ما عندهاش edge قريب، ممكن restore يكون أبطأ بـ 200-400ms في الـ TTFB. للـ backup ده مش فارق جوهري، للـ live serving فارق بيتحس.
- التوافق مع Tools: 95% من S3 tools بتشتغل بدون تعديل، لكن بعض المميزات المتقدمة مش متاحة على R2 لحد دلوقتي — تحديدًا S3 Object Lock (للـ WORM compliance) و Lambda Triggers على الـ events. لو كنت بتعتمد عليها، احسبها قبل ما تنقل.
- الـ Encryption at Rest: R2 بتعمل AES-256 encryption تلقائي، لكن مش بتدّيك Customer-Managed Keys زي AWS KMS. لو لازم compliance بمفتاح تتحكم فيه (HIPAA strict mode، أو متطلبات بنوك معينة)، AWS أنسب.
- الـ Regional Redundancy: R2 بتعمل replication داخل المنطقة الواحدة افتراضيًا. AWS S3 Standard بـ multi-AZ replication. عمليًا الاتنين بيدّوا 99.999999999% durability (11 nines)، الفرق بيظهر في الـ availability SLA: AWS 99.99% مقابل Cloudflare 99.9%. الفرق ساعتين downtime زيادة في السنة.
متى لا تستخدم Cloudflare R2 لـ Backups
- لو infrastructure بتاعك كله على AWS وبتعمل VPC peering معقّد — التعقيد التشغيلي مش يستاهل لو الفرق $30 شهريًا.
- لو DB صغير (أقل من 5 GB) وعدد restores قليل — الفرق هيكون أقل من $5 شهريًا، مش يستاهل migration.
- لو محتاج Object Lock للـ WORM compliance (Worm = Write Once Read Many) — لسّه مش متاح على R2.
- لو بتستخدم AWS Backup الـ managed service بـ Point-in-Time Recovery — ده مستوى تاني من الـ backups (continuous WAL archiving)، السكربت ده بيعمل dump-based فقط، بيقدر يرجّعك لآخر يوم مش لآخر ثانية.
الخطوة التالية
افتح Cloudflare Dashboard دلوقتي، روح Storage > R2، واعمل bucket جديد بإسم db-backups-prod. بعدين انسخ السكربت من فوق، حط الـ secrets في GitHub repository settings، وشغّل الـ workflow يدويًا مرة بـ workflow_dispatch. لو الرفع نجح في أقل من 10 دقايق، فعّل الـ schedule. لو فشل، الخطأ الأكتر شيوعًا اللي بيقابلني: الـ R2_ACCOUNT_ID فيه شرطة (-) لما بتنسخه من الواجهة — شيلها وحاول تاني.
المصادر
- Cloudflare R2 Pricing الرسمي: developers.cloudflare.com/r2/pricing
- AWS S3 Pricing الرسمي: aws.amazon.com/s3/pricing
- PostgreSQL pg_dump documentation: postgresql.org/docs/16/app-pgdump
- rclone S3-compatible providers (Cloudflare R2): rclone.org/s3/#cloudflare-r2
- AWS Signature Version 4 specification: docs.aws.amazon.com/IAM/latest/UserGuide/reference_aws-signing
- Veeam Data Protection Trends 2024 (إحصائية فشل الاستعادة): veeam.com/data-protection-trends-executive-brief
- GitHub Actions billing & minutes: docs.github.com/billing/managing-billing-for-github-actions