أتمتة backup PostgreSQL على S3 بـ GitHub Actions
لو بتعتمد على cron داخل السيرفر علشان تاخد backup لـ PostgreSQL، أنت عندك single point of failure واضح. لو السيرفر وقع، الـ backup بتاعك وقع معاه. الحل: خلي GitHub Actions هو اللي يشغّل الـ backup يوميًا ويرفعه على S3، من غير ما تدفع لأي سيرفر إضافي، ومن غير ما تحتاج تدخل يدوي.
المشكلة باختصار
أغلب المشاريع الصغيرة والمتوسطة بتعمل backup بـ pg_dump داخل cron على نفس السيرفر اللي فيه الـ database. الإعداد ده بيخلق 3 مشاكل مترابطة: الـ backup بيتخزن على نفس القرص، لو السيرفر اتحرق أنت خسرت الـ backup مع الـ DB في نفس اللحظة، ولو الـ disk امتلأ الـ cron بيفشل بصمت من غير ما حد يلاحظ. GitHub Actions بيحل التلاتة دفعة واحدة لأنه معزول تمامًا عن الـ infrastructure اللي بتحميه.
تمثيل بسيط الأول
تخيّل إنك بتاخد نسخة من مفتاح بيتك كل يوم وبتسيبها على مكتبك في الشركة. كويس، بس لو حصل حريق في الشركة خسرت النسخة والمكتب في نفس اللحظة. لما تبدأ تبعت النسخة لصديق ساكن في حي تاني، أنت عملت off-site backup. ده بالظبط اللي بنعمله هنا: الـ S3 هو الصديق في الحي التاني، والـ GitHub Actions هو الساعي اللي بيوصل النسخة كل يوم الساعة 2 الفجر من غير ما تفتكره.
ليه GitHub Actions بالذات مش cron على السيرفر
فيه 3 أسباب عملية تخليك تفضّل الخيار ده:
- مجاني عمليًا. كل حساب GitHub مجاني بياخد 2000 دقيقة runners في الشهر. الـ backup job الواحد بياخد من دقيقة لـ 3 دقايق على DB بحجم 5GB. يعني 30 backup في الشهر ≈ 90 دقيقة. أنت لسه بعيد جدًا عن الحد المسموح.
- معزول عن الـ infrastructure. لو سيرفر الـ DB وقع أو اتغلق، الـ workflow لسه بيشتغل من GitHub runners تانية. الـ failure notification بيوصلك عبر GitHub نفسه على الإيميل، وتقدر توصله لـ Slack بخطوة واحدة.
- Secrets management جاهز. بتحط الـ AWS keys والـ DB password في GitHub Secrets، مش في ملف
.envعلى السيرفر أو سطر cron فيه password ظاهر.
المتطلبات قبل ما تبدأ
- Bucket على S3 مع versioning مفعّل علشان لو اتكتب فوق ملف بالغلط تقدر ترجع نسخة قديمة.
- IAM user بصلاحية
s3:PutObjectعلى الـ bucket ده بس، مش على كل S3 — principle of least privilege. - DB reachable من الـ internet، أو عبر bastion/VPN — هنتكلم على ده في قسم الـ trade-offs.
الـ workflow الكامل
حط الملف ده في المسار .github/workflows/postgres-backup.yml داخل الريبو بتاعك:
name: Postgres Daily Backup
on:
schedule:
- cron: '0 2 * * *'
workflow_dispatch:
jobs:
backup:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Install postgresql-client
run: |
sudo apt-get update
sudo apt-get install -y postgresql-client-16
- name: Dump database
env:
PGPASSWORD: ${{ secrets.DB_PASSWORD }}
run: |
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
pg_dump \
-h ${{ secrets.DB_HOST }} \
-U ${{ secrets.DB_USER }} \
-d ${{ secrets.DB_NAME }} \
-F c -Z 9 \
-f backup-$TIMESTAMP.dump
echo "FILE=backup-$TIMESTAMP.dump" >> $GITHUB_ENV
- name: Upload to S3
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_KEY }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET }}
AWS_DEFAULT_REGION: eu-west-1
run: |
aws s3 cp $FILE s3://my-db-backups/postgres/$FILE \
--storage-class STANDARD_IAالـ flag بتاع -F c -Z 9 في pg_dump بيعمل custom format مع أعلى مستوى ضغط. ده بيقلل حجم الملف النهائي بين 60% و 80% مقابل زيادة زمن CPU دقيقة أو اتنين. الـ trade-off هنا كويس جدًا لأن الزمن ده على GitHub runner مش على السيرفر بتاعك.
استعادة الـ backup (drill مهم جدًا)
ركز في النقطة دي: backup ما اتعملش عليه restore فعلي = مفيش backup. معظم الفرق بتكتشف إن الـ dump file تالف أو ناقص جداول في أسوأ لحظة ممكنة. خصّص يوم في الشهر لتشغيل drill:
# نزّل آخر backup من S3
aws s3 cp s3://my-db-backups/postgres/backup-20260419-020015.dump .
# أنشئ DB اختباري محلي
createdb restore_test
# رجّع الـ dump بـ 4 workers متوازيين
pg_restore -d restore_test -j 4 backup-20260419-020015.dump
# تحقق إن الجداول الأساسية موجودة وبيها بيانات
psql -d restore_test -c "\dt"
psql -d restore_test -c "SELECT COUNT(*) FROM users;"لو الـ COUNT رجع صفر على جدول أنت عارف إن فيه بيانات، الـ dump مش سليم. أوقف النشر واعرف السبب قبل ما تكمل.
trade-offs لازم تعرفها قبل ما تنفّذ
الطريقة دي ليها 3 تكاليف صريحة مش مخفية:
- الـ DB لازم يكون reachable من الـ internet. لو DB جوا VPC خاص، محتاج تضيف step يفتح SSH tunnel عبر bastion host، وده بيزوّد تعقيد الـ workflow وزمن التنفيذ بـ 20 لـ 40 ثانية إضافية.
- GitHub Actions logs بتحتفظ بمخرجات الـ runs لـ 90 يوم. متطبعش passwords أو connection strings أبدًا في الـ run logs. استخدم
::add-mask::لو محتاج تلوق متغير حسّاس. - S3 costs واقعية بس حقيقية. 30 backup في الشهر × 500MB × 12 شهر = 180GB في STANDARD_IA ≈ $2.25 في الشهر. بسيط، بس راقبه بـ lifecycle rule يحذف الملفات الأقدم من 90 يوم تلقائيًا.
متى لا تستخدم هذه الطريقة
لو الـ DB عندك ≥ 100GB، الـ workflow ممكن يعدي الـ 6 ساعات limit لـ GitHub Actions المجاني وده هيوقع الـ job. في الحالة دي استخدم pg_basebackup مع WAL archiving على السيرفر نفسه ورفعها بـ rclone لـ S3. كمان لو عندك requirement قانوني بـ Point-In-Time Recovery (backup كل 15 دقيقة)، GitHub Actions مش الأداة الصح — محتاج أدوات مخصصة زي WAL-G أو pgBackRest. الشرح ده مبني على افتراض إن DB حجمه بين 500MB و 50GB، وهو النطاق اللي بيغطي 80% من المشاريع.
الخطوة التالية
أنشئ الـ workflow النهاردة بالظبط كما هو، شغّله يدويًا بـ workflow_dispatch من تاب Actions في GitHub، وتأكد إن الملف ظهر في الـ bucket. بعدها حدد تاريخ drill أسبوعي على التقويم بتاعك لاستعادة آخر backup على DB اختباري. الـ backup اللي محدش جرّب يرجّعه هو مجرد ملف بياخد فلوس S3 كل شهر.
مصادر
- PostgreSQL
pg_dumpdocumentation — postgresql.org/docs/current/app-pgdump.html - GitHub Actions scheduled workflows — docs.github.com/en/actions — events-that-trigger-workflows
- AWS S3 storage classes والتكلفة — aws.amazon.com/s3/storage-classes
- GitHub Actions usage limits and billing — docs.github.com/en/actions — usage-limits
- AWS IAM best practices for least privilege — docs.aws.amazon.com/IAM — best-practices