> ## Content Index
> Fetch the complete content index at: https://arabroot.io/llms.txt
> Use this file to discover other available public pages before exploring further.

# Gitea Actions لتشغيل CI/CD على السيرفر وبناء Docker Images ونشرها
- URL: https://arabroot.io/articles/gitea-actions-تشغيل-ci-cd-على-خادمك/
- Published: 2026-09-27T15:50:00.000Z
- Updated: 2026-10-04T20:52:38.000Z
- Description: اجعل خادمك يختبر الكود ويبني Docker Images تلقائياً مع كل push، دون الاعتماد على خدمة CI خارجية. نفعل Gitea Actions، ونشغل Runner معزولاً، ونكتب أول workflow خطوة بخطوة.
- Author: فريق عرب رووت
- Tags: الأدلة التقنية, DevOps وCI/CD, الاستضافة الذاتية, Gitea

لديك الآن [سيرفر Gitea](https://arabroot.io/articles/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-gitea-%D9%85%D8%B9-docker-compose/) يحفظ مستودعات Repositories الكود الخاصة بك، وبعد كل push تفتح الطرفية Terminal على جهازك وتنفذ `docker build` ثم `docker push` بنفسك، وهذه الطريقة تعمل في الأيام الأولى ولكنها تفشل بمرور الوقت، والسبب أن البناء Build يعتمد على جهاز شخص واحد وما عليه من إصدارات، وأن الباسورد الخاص بالـ Registry محفوظ على ذلك الجهاز، وأنك سوف تنسى يوماً ما أن ترفع الـ Image بعد تعديل صغير، فيعمل سيرفر الإنتاج Production بنسخة قديمة دون أن ينتبه أحد.

والحل المعتاد بعد ذلك هو خدمة CI/CD خارجية مثل GitHub Actions أو GitLab CI، وهي تحل مشكلة التكرار، ولكن الكود والأسرار Secrets تخرج من سيرفرك إلى سيرفرات تلك الخدمة مع كل تشغيل Run، وهذا لا يناسب من يستضيف خدماته بنفسه لأنه اختار ذلك أصلاً حتى تبقى بياناته عنده. لذلك فالحل الأنسب أن يتولى سيرفرك هذا العمل بنفسه عبر [**Gitea Actions**](https://docs.gitea.com/usage/actions/overview/?ref=arabroot.io)، وهو نظام CI/CD مدمج في Gitea وملفاته مكتوبة [بصيغة GitHub Actions نفسها تقريباً](https://docs.gitea.com/usage/actions/comparison/?ref=arabroot.io)، حيث تضعها في المجلد `.gitea/workflows/` وتستخدم فيها المفاتيح Keys `on:` و`jobs:` و`steps:`، ومعظم ال actions المنشورة على GitHub تعمل فيه دون تعديل.

ولكن لاحظ أن Gitea لا ينفذ المهام Jobs بنفسه، وإنما يوزعها على برنامج منفصل اسمه [**Gitea Runner**](https://docs.gitea.com/runner/?ref=arabroot.io)، وهو الذي يسحب المهام وينفذ كل مهمة في Container مستقل، وكان اسمه act\_runner حتى الإصدار 0.6، ثم [تغير اسمه في مايو 2026 مع الإصدار 1.0](https://blog.gitea.com/release-of-runner-1.0.0/?ref=arabroot.io) فأصبحت الـ Image الخاصة به باسم `gitea/runner`.

وسوف نناقش في هذا المقال ما يلي:

- تفعيل Actions في Gitea، والحصول على رمز التسجيل Registration Token للـ runner.
- اختيار نوع الـ Image الخاصة بالـ runner، ولماذا نشغله بطريقة Docker-in-Docker بدلاً من تمرير Docker السيرفر إليه، وما الذي لا يحميك منه هذا العزل.
- كتابة أول workflow يبني Docker Image ويرفعها إلى [**الـ Container Registry المدمج في Gitea**](https://docs.gitea.com/usage/packages/container/?ref=arabroot.io) Packages، مستخدماً الأسرار والمتغيرات Variables.
- خطوة نشر اختيارية، ثم التحقق من النجاح والنسخ الاحتياطي والتحديث وأشهر المشكلات وحلولها.

## ما تحتاجه قبل أن تبدأ

- Gitea يعمل على `https://git.example.com` وقيمة `ROOT_URL` مضبوطة كما في دليل التثبيت، والسبب أن الـ runner يتصل بهذا العنوان، ومنه يستنسخ Clone المستودعات ويرفع الـ Images.
- سيرفر للـ runner، والسيرفر نفسه يكفي لفريق صغير، ولكن عندما تكثر عمليات البناء ننصح بسيرفر منفصل لأنها تستهلك المعالج CPU والقرص Disk، وكبداية خصص له 2 vCPU و4 GB من الذاكرة RAM و30 GB على الأقل لتخزين الـ Images مؤقتاً.
- Docker Engine على سيرفر الـ runner، وإذا لم تثبته بعد فابدأ بدليل [تثبيت Docker على Ubuntu](https://arabroot.io/articles/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-docker-%D8%B9%D9%84%D9%89-ubuntu/).

## تفعيل Actions في Gitea

ميزة Actions مفعلة افتراضياً منذ [Gitea 1.21](https://github.com/go-gitea/gitea/releases/tag/v1.21.0?ref=arabroot.io)، وبالتالي لن تحتاج غالباً إلى أي تعديل، ولكن إذا كانت معطلة في نسختك، أي أنك لا ترى تبويب **Actions** في المستودع ولا في Site Administration، فأضف هذين السطرين إلى خدمة gitea في ملف Compose ثم نفذ `docker compose up -d`، والمفتاحان مشروحان في [مرجع الإعدادات](https://docs.gitea.com/administration/config-cheat-sheet/?ref=arabroot.io) Configuration Cheat Sheet:

```yaml
      GITEA__actions__ENABLED: "true"
      # من أين تجلب actions المكتوبة بدون عنوان كامل (مثل actions/checkout@v4)
      GITEA__actions__DEFAULT_ACTIONS_URL: github
```

والمفتاح الثاني يحدد المكان الذي يجلب منه Gitea ال actions التي تكتبها دون عنوان كامل، فالقيمة `github` تعني موقع GitHub، أما `self` فتعني سيرفر Gitea نفسه. بعد ذلك فعل Actions لكل مستودع من **Settings ← Repository ← Advanced Settings ← Actions** إن لم تكن مفعلة، وتأكد أن سجل الحزم Packages مفعل أيضاً، وهو مفعل افتراضياً.

## الحصول على رمز التسجيل Registration Token

يسجل الـ runner نفسه مرة واحدة برمز يصدره Gitea، وللرمز [عدة مستويات](https://docs.gitea.com/runner/registration/?ref=arabroot.io): المستوى **Global** لكل المستودعات ويصدر من Site Administration، ومستوى المنظمة Organization أو المستخدم User، ومستوى **المستودع** الواحد ويصدر من إعداداته. وسوف نبدأ بالمستوى Global، لذلك افتح **Site Administration ← Actions ← Runners ← Create new Runner** وانسخ **Registration Token**.

![صفحة Runners Management في لوحة إدارة Gitea مع قائمة Create new Runner ورمز التسجيل](https://arabroot.io/content/images/2026/09/gitea-actions-01-runner-registration-token.webp)

رمز تسجيل runner على مستوى الموقع بأكمله

والرمز الواحد يسجل أكثر من runner ويبقى صالحاً حتى تعيد تعيينه، فإذا تسرب فاضغط **Reset registration token**، ولن تتأثر الـ runners المسجلة من قبل لأن كل واحد منها يحفظ بيانات الاعتماد Credentials الخاصة به بعد التسجيل.

## تشغيل Gitea Runner في Container

### اختيار نوع الـ Image

للـ [Image الخاصة بالـ runner](https://hub.docker.com/r/gitea/runner?ref=arabroot.io) ثلاثة أنواع من الوسوم Tags، والفرق بينها مشروح في [دليل التشغيل بـ Docker](https://docs.gitea.com/runner/installation/docker/?ref=arabroot.io)، ويتلخص في المكان الذي تعمل فيه Containers المهام:

| الوسم                            | أين تعمل Containers المهام                       | متى تختاره                                                                                                                                                   |
| -------------------------------- | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| gitea/runner:4.1.0               | على Docker السيرفر نفسه عبر /var/run/docker.sock | الأبسط والأسرع، ولكن أي workflow يرى كل Containers السيرفر ويتحكم فيها، فلا تختره إلا إذا كنت تثق تماماً بكل من يرسل كوداً إلى المستودعات.                   |
| gitea/runner:4.1.0-dind          | داخل Docker خاص بالـ runner، أي Docker-in-Docker | عزل Isolation أفضل، فالمهام لا ترى Containers السيرفر، ويتطلب الوضع privileged، وهو ما نستخدمه في هذا الدليل.                                                |
| gitea/runner:4.1.0-dind-rootless | Docker خاص يعمل دون صلاحيات root                 | الأكثر أماناً، مع [القيود المعروفة لوضع rootless](https://docs.docker.com/engine/security/rootless/?ref=arabroot.io) على الشبكة Networking والتخزين Storage. |

وقد يتساءل البعض: لماذا لا نختار النوع الأول وهو الأبسط؟ والإجابة أن تمرير `/var/run/docker.sock` يعني أن أي workflow يستطيع أن يرى كل Containers السيرفر ويوقفها ويقرأ متغيراتها، بل ويشغل Container جديداً يصل إلى ملفات السيرفر بصلاحيات root، وبالتالي فكل من يملك صلاحية push في أي مستودع يملك السيرفر بالكامل، أما النوع dind فيشغل Docker مستقلاً داخل Container الـ runner، فلا ترى المهام إلا ما بداخله.

ولكن تذكر أن هذا العزل يحميك من workflow يعبث بخدماتك بالخطأ، وليس حاجزاً أمنياً أمام كود خبيث، والسبب أن Container الـ runner يعمل بالوضع `privileged` الذي [يعطيه تقريباً كل صلاحيات السيرفر](https://docs.docker.com/reference/cli/docker/container/run/?ref=arabroot.io#privileged)، وأي مهمة تستطيع أن تطلب من Docker الداخلي تشغيل Container بنفس الوضع ثم تصل منه إلى أجهزة السيرفر. لذلك لا تشغل على هذا الـ runner إلا كوداً تثق به، ولاحظ أن Gitea [يطلب موافقة Approval](https://docs.gitea.com/usage/actions/faq/?ref=arabroot.io) قبل تشغيل ال workflows في طلبات الدمج Pull Requests القادمة من Fork لشخص لا يملك صلاحية الكتابة، فلا توافق عليها قبل أن تقرأ ما عدله في المجلد `.gitea/workflows/`، وإذا كان المستودع عاماً ويستقبل مساهمات من أشخاص لا تعرفهم فخصص له runner على سيرفر منفصل لا يحمل أي خدمة أخرى.

### الملفات

نبدأ بإنشاء مجلد للـ runner على السيرفر:

```bash
sudo mkdir -p /opt/gitea-runner
cd /opt/gitea-runner
```

بعد ذلك ننشئ ملف `.env` وفيه رمز التسجيل وحده:

```ini
GITEA_RUNNER_REGISTRATION_TOKEN=الصق-الرمز-هنا
```

ثم ملف `config.yaml`، وأهم ما فيه [الـ labels](https://docs.gitea.com/runner/labels/?ref=arabroot.io) التي تربط ما يكتبه الـ workflow في `runs-on` بالـ Image التي تنفذ المهمة، وسوف نعرف هنا اثنين: الأول `ubuntu-latest` ويستخدم [الـ Image الجاهزة من Gitea](https://gitea.com/gitea/runner-images?ref=arabroot.io)، وفيها Node وgit وأدوات كثيرة ولكنها كبيرة الحجم، والثاني `docker` ويستخدم [الـ Image docker:cli](https://hub.docker.com/%5F/docker?ref=arabroot.io) الصغيرة، وهي تكفي لبناء الـ Images ورفعها:

```yaml
log:
  level: info
runner:
  capacity: 1
  timeout: 1h
  labels:
    - "ubuntu-latest:docker://docker.gitea.com/runner-images:ubuntu-latest"
    - "docker:docker://docker:29.8.1-cli"
container:
  network: ""
  privileged: false
```

في الإعداد أعلاه لاحظ التالي:

- `capacity: 1` يعني أن الـ runner ينفذ مهمة واحدة في نفس الوقت، فارفع القيمة إذا كان السيرفر يتحمل أكثر من عملية بناء.
- ترك `network` فارغاً يجعل الـ runner ينشئ شبكة Network خاصة بكل مهمة، ولا تضع فيه `bridge`، والسبب أن المهام على تلك الشبكة لم تعد تستطيع منذ [الإصدار 4.0](https://gitea.com/gitea/runner/releases/tag/v4.0.0?ref=arabroot.io) الوصول إلى ال cache ولا رفع ال artifacts.
- `privileged: false` خاص بـ Containers المهام وليس بالـ runner نفسه، فالمهام لا تحتاج هذا الوضع لأنها تستخدم Docker الخاص بالـ runner.

وأخيراً ملف `docker-compose.yml`:

```yaml
services:
  runner:
    image: gitea/runner:4.1.0-dind
    restart: unless-stopped
    privileged: true
    env_file: .env
    environment:
      GITEA_INSTANCE_URL: https://git.example.com/
      GITEA_RUNNER_NAME: runner-01
      CONFIG_FILE: /config/config.yaml
    volumes:
      - ./config.yaml:/config/config.yaml:ro
      # ملف التسجيل .runner
      - runner-data:/data
      # Docker Images الخاصة بالمهام، حتى لا تسحب من جديد بعد كل إعادة تشغيل
      - runner-docker:/var/lib/docker

volumes:
  runner-data:
  runner-docker:
```

في الإعداد أعلاه لاحظ التالي:

- `privileged: true` شرط لعمل Docker الداخلي في النوع dind، وهو سبب التحذير الذي ذكرناه في القسم السابق.
- قيمة `GITEA_INSTANCE_URL` هي نفس قيمة ROOT\_URL في سيرفر Gitea.
- ال Volume `runner-data` يحفظ ملف التسجيل `.runner`، وال Volume `runner-docker` يحفظ ال Images التي تسحبها المهام حتى لا تسحبها من جديد بعد كل إعادة تشغيل.

الآن سوف نقوم بتشغيل الـ runner ومتابعة السجل Log الخاص به:

```bash
docker compose up -d
docker compose logs -f runner
```

في التشغيل الأول يسجل الـ runner نفسه ثم يبدأ العمل، والمخرج سوف يكون كما يلي:

```bash
level=info msg="Runner registered successfully."
level=info msg="runner: runner-01, with version: v4.1.0, with labels: [ubuntu-latest docker], declare successfully"
```

وسوف تجد الـ runner في **Site Administration ← Actions ← Runners** بحالة Idle:

![قائمة الـ runners في Gitea وفيها runner-01 بحالة Idle والإصدار v4.0.0 والوسوم ubuntu-latest وdocker](https://arabroot.io/content/images/2026/09/gitea-actions-02-runner-online.webp)

الـ runner مسجل ومتصل

💡

بعد التسجيل يحفظ الـ runner بيانات الاعتماد الخاصة به في الملف `/data/.runner` داخل ال Volume، ولا يعود إلى رمز التسجيل مرة أخرى، لذلك تستطيع حذف الرمز من `.env`، ولكن إذا حذفت ال Volume فسوف تحتاج إلى رمز جديد، وسيظهر الـ runner في القائمة كأنه جهاز جديد.

## الأسرار والمتغيرات

حتى يرفع الـ workflow الـ Image يحتاج إلى تسجيل الدخول Login في الـ Registry الخاص بـ Gitea، ولا تستخدم الباسورد الخاص بحسابك لهذا الغرض، والسبب أن من يحصل عليه يستطيع فعل كل شيء باسمك، وإنما أنشئ رمز وصول Access Token مخصصاً من **Settings ← Applications ← Generate New Token** بصلاحية Scope [write:package](https://docs.gitea.com/development/oauth2-provider/?ref=arabroot.io) وحدها، فهي تكفي لتسجيل الدخول ورفع الـ Images، وإذا تسرب الرمز فلن يستطيع من يحمله قراءة الكود أو تعديله. بعد ذلك أضف الرمز إلى المستودع من **Settings ← Actions ← Secrets ← Add secret**:

- `REGISTRY_TOKEN`: الرمز الذي أنشأته.

ولاحظ أن [أسماء الأسرار](https://docs.gitea.com/usage/actions/secrets/?ref=arabroot.io) لا تقبل إلا الحروف الإنجليزية والأرقام والعلامة \_، ولا تبدأ برقم ولا بالبادئة `GITHUB_` أو `GITEA_`، فاسم مثل `GITEA_TOKEN` مرفوض لأنه محجوز للرمز الذي يصدره Gitea تلقائياً لكل مهمة.

![صفحة Secrets Management في إعدادات المستودع وفيها السر REGISTRY_TOKEN](https://arabroot.io/content/images/2026/09/gitea-actions-03-repo-secrets.webp)

السر محفوظ، ولا تعرض الواجهة قيمته

أما اسم المستخدم فليس سراً، لذلك ضعه في **Variables** باسم `REGISTRY_USER`:

![صفحة Variables Management وفيها المتغير REGISTRY_USER بقيمة gitadmin](https://arabroot.io/content/images/2026/09/gitea-actions-04-repo-variables.webp)

تظهر قيم المتغيرات، ويقرؤها الـ workflow عبر vars

ولماذا نفصل بينهما؟ والسبب أن Gitea يخفي قيمة كل سر في السجلات ويضع مكانها `***`، فلو وضعت اسم المستخدم في Secret لاختفى اسم الحساب من كل موضع في السجل، حتى من مسار الـ Image، لذلك اجعل الأسرار للقيم السرية وحدها. وتستطيع أيضاً تعريف الأسرار والمتغيرات على مستوى المنظمة، فتصل إليها كل مستودعاتها.

## أول workflow يبني Image ويرفعها

ضع في المجلد الرئيسي للمستودع ملف `Dockerfile` بسيطاً للتجربة، ومعه ملف `index.html` بأي محتوى:

```dockerfile
FROM nginx:1.29-alpine
COPY index.html /usr/share/nginx/html/index.html
```

ثم أنشئ الملف `.gitea/workflows/ci.yml` كما يلي:

```yaml
name: build-and-push

on:
  push:
    branches: [main]
  workflow_dispatch:

env:
  REGISTRY: git.example.com
  IMAGE: git.example.com/${{ github.repository }}

jobs:
  image:
    runs-on: docker
    defaults:
      run:
        shell: sh
    steps:
      - name: Checkout
        uses: builtin:checkout

      - name: Log in to the Gitea registry
        run: echo "${{ secrets.REGISTRY_TOKEN }}" | docker login "$REGISTRY" -u "${{ vars.REGISTRY_USER }}" --password-stdin

      - name: Build
        run: |
          TAG=$(echo "$GITHUB_SHA" | cut -c1-8)
          docker build -t "$IMAGE:$TAG" -t "$IMAGE:latest" .
          echo "TAG=$TAG" >> "$GITHUB_ENV"

      - name: Push
        run: |
          docker push "$IMAGE:$TAG"
          docker push "$IMAGE:latest"

      - name: Log out
        if: always()
        run: docker logout "$REGISTRY"
```

في الإعداد أعلاه لاحظ التالي:

- `runs-on: docker` يختار الـ label الذي عرفناه، فتعمل المهمة في `docker:29.8.1-cli`، وهذه الـ Image مبنية على Alpine وليس فيها bash، لذلك حددنا `shell: sh`.
- `builtin:checkout` ميزة جديدة في [Runner 4](https://gitea.com/gitea/runner/releases/tag/v4.0.0?ref=arabroot.io)، حيث تنسخ المستودع دون تنزيل أي action ودون أن تحتاج Image المهمة إلى Node، وهي تقبل جزءاً من خيارات `actions/checkout` فقط، أما مع الـ Image `ubuntu-latest` فتستطيع استخدام `actions/checkout@v4` المعتاد.
- يمرر الـ runner خدمة Docker الخاصة به إلى Container المهمة تلقائياً، فيعمل `docker build` مباشرة، والبناء يجري داخل Docker المعزول الخاص بالـ runner وليس على السيرفر.
- عنوان الـ Image في الـ Registry الخاص بـ Gitea يكون دائماً بالصيغة `git.example.com/<owner>/<image>:<tag>`، و`github.repository` يعطيك القيمة `owner/repo`، ولكن Docker لا يقبل الحروف الكبيرة في أسماء الـ Images، فإذا كان اسم المستخدم أو المستودع يحتوي على حرف كبير فسوف يفشل البناء بخطأ يقول `repository name must be lowercase`، والحل أن تسمي المستودع بحروف صغيرة أو تكتب اسم الـ Image صراحة في `IMAGE`.

وقبل أن تضيف أي action من GitHub إلى الـ workflow تذكر أنها كود يعمل على الـ runner الخاص بك ويرى كل الأسرار التي تمررها للمهمة، لذلك ثبتها على الهاش الكامل للـ commit بدلاً من وسم مثل `@v4`، والسبب أن صاحب المستودع، أو من يخترق حسابه، يستطيع نقل الوسم إلى كود آخر في أي وقت، أما الهاش فلا يتغير.

📌

في مارس 2025 نقل مخترقون وسوم الإصدارات في ال action الشهيرة tj-actions/changed-files، وهي مستخدمة في أكثر من 23 ألف مستودع على GitHub، إلى commit خبيث يقرأ ذاكرة الـ runner ويطبع الأسرار التي فيها داخل سجلات البناء، فأصبحت تلك الأسرار مكشوفة لكل من يفتح سجلات المستودعات العامة، وسجلت الثغرة باسم [CVE-2025-30066](https://github.com/advisories/GHSA-mrrh-fwg8-r2c3?ref=arabroot.io). والدرس أن الوسم مجرد اسم يمكن تغييره، وأن كل action تضيفها تعمل بنفس صلاحيات الـ workflow، لذلك اقرأ ما تضيفه وثبته على هاش commit معروف.

الآن أضف الملفات إلى المستودع ونفذ push:

```bash
git add Dockerfile index.html .gitea/workflows/ci.yml
git commit -m "ci: build and push image"
git push
```

افتح تبويب **Actions** في المستودع، وسوف تجد أن التشغيل الأول أبطأ لأن معظم وقته يذهب في سحب الـ Image `docker:cli` والـ Image الخاصة بـ nginx، أما التشغيلات التالية فأسرع لأن هذه ال Images محفوظة في ال Volume `runner-docker`:

![تشغيل ناجح للـ workflow في Gitea مع الخطوات Checkout وLog in وBuild وPush وسجل خطوة Push](https://arabroot.io/content/images/2026/09/gitea-actions-05-run-success-log.webp)

تشغيل ناجح، وسجل خطوة الرفع إلى الـ Registry

وبعد انتهاء التشغيل سوف تجد الـ Image في **Packages** الخاصة بالمالك Owner بوسمين: `latest`، وأول ثمانية أحرف من هاش الـ commit:

![صفحة حزمة الـ Container demo-app في Gitea مع أمر docker pull والإصدارين latest والوسم المختصر للـ commit](https://arabroot.io/content/images/2026/09/gitea-actions-06-container-package.webp)

الـ Image في الـ Container Registry الخاص بـ Gitea، والعنوان في الصورة محلي، أما في بيئة الإنتاج فيكون دون منفذ Port

بعد ذلك اربط الحزمة بالمستودع من **Settings** في صفحة الحزمة حتى تظهر في تبويب Packages الخاص به، واجعلها خاصة Private إذا كان الكود خاصاً، ولسحبها على سيرفر الإنتاج نفذ:

```bash
docker login git.example.com
docker pull git.example.com/gitadmin/demo-app:latest
```

## خطوة النشر Deployment الاختيارية

بعد رفع الـ Image تستطيع أن تطلب من سيرفر الإنتاج سحبها وتشغيلها، وأبسط طريقة لذلك webhook تستدعيه الخطوة الأخيرة في الـ workflow، مثل [webhook لـ stack في Portainer](https://docs.portainer.io/user/docker/stacks/webhooks?ref=arabroot.io) أو خدمة صغيرة على السيرفر:

```yaml
      - name: Deploy
        run: wget -q -O- --post-data="" "${{ secrets.DEPLOY_WEBHOOK_URL }}"
```

واحفظ رابط ال webhook في الأسرار وليس في الملف نفسه، والسبب أن من يملك هذا الرابط يستطيع إعادة نشر الخدمة دون أي صلاحية أخرى.

## التحقق من النجاح

- الـ runner بحالة Idle في قائمة الـ runners، وآخر ظهور له «now».
- كل push إلى `main` ينشئ تشغيلاً جديداً في تبويب Actions، وينتهي بعلامة خضراء.
- الحزمة تظهر بوسم جديد، و`docker pull` يعمل من جهاز آخر بعد `docker login`.
- قيمة الرمز لا تظهر في سجل خطوة Login، وإنما يظهر مكانها `***`.

## النسخ الاحتياطي Backup والاستعادة Restore

ملفات ال workflows وحالة التشغيلات والأسرار والمتغيرات والحزم كلها يحفظها Gitea في قاعدة البيانات Database وفي المجلد `/data/gitea/packages`، والأمر `gitea dump` المشروح في دليل التثبيت يغطيها جميعاً، ولكن انتبه إلى حجم الحزم لأن ال Docker Images تكبر بسرعة، لذلك راقب المساحة وأضف قواعد تنظيف Cleanup Rules من **Settings ← Packages ← Cleanup rules**، مثل الاحتفاظ بآخر 10 وسوم.

أما الـ runner نفسه فلا يحتاج إلى نسخ احتياطي، ويكفي أن تحفظ `config.yaml` و`docker-compose.yml`، وإذا فقدت ال Volume فسجل الـ runner من جديد برمز جديد واحذف القديم من القائمة.

## التحديث Upgrade إلى إصدار أحدث

غير وسم الـ Image في `docker-compose.yml`، ثم نفذ:

```bash
docker compose pull
docker compose up -d
```

والتسجيل محفوظ في Volume البيانات، فلا يحتاج الـ runner إلى رمز جديد، ولكن قبل الانتقال بين الإصدارات الرئيسية Major Versions اقرأ ملاحظات الإصدار Release Notes و[دليل الترقية](https://docs.gitea.com/runner/upgrade/?ref=arabroot.io)، فمثلاً في [الإصدار 4.0](https://gitea.com/gitea/runner/releases/tag/v4.0.0?ref=arabroot.io) لم يعد الرمز `*` في `valid_volumes` يطابق المسارات المتداخلة Nested Paths وأصبح ذلك بالرمز `**`، وتغيرت أيضاً طريقة وصول المهام إلى ال cache. وإذا كنت تنتقل من act\_runner القديم فاسم الـ Image أصبح `gitea/runner` بدلاً من `gitea/act_runner`، واسم الملف التنفيذي Binary أصبح `gitea-runner`.

وإذا كنت سوف تنقل Gitea نفسه إلى [الإصدار 28.0](https://github.com/go-gitea/gitea/releases/tag/v28.0.0?ref=arabroot.io) الذي صدر في 29 سبتمبر 2026، فاعلم أن الملفات في هذا الدليل تعمل معه كما هي، ولكنه يحذف تلقائياً التشغيلات المكتملة التي مضى عليها أكثر من 400 يوم عبر الإعداد الجديد `RUN_RETENTION_DAYS`، فإذا كنت تحتاج سجلاتها لفترة أطول فارفع القيمة أو اجعلها 0 قبل الترقية.

## مشكلات شائعة وحلولها

### التشغيل عالق على «Waiting» ولا يبدأ

السبب واحد من اثنين: لا يوجد runner يحمل الـ label المطلوب في `runs-on`، أو أن الـ runner غير متصل، لذلك قارن الـ labels في قائمة الـ runners بما كتبته في الـ workflow، وتذكر أن الـ runner المسجل على مستوى مستودع لا يرى المستودعات الأخرى.

### فشل التسجيل: «unregistered runner» أو «connection refused»

قيمة `GITEA_INSTANCE_URL` يجب أن تطابق ROOT\_URL، ولا تستخدم `localhost`، والسبب أنه داخل Container الـ runner يشير إلى الـ Container نفسه وليس إلى السيرفر، وتحقق أيضاً أن أحداً لم يقم بإعادة تعيين الرمز.

### «exec: bash: executable file not found»

Image المهمة ليس فيها bash، وهذا حال الـ Images المبنية على Alpine، والحل أن تضيف `defaults.run.shell: sh` أو تستخدم الـ Image `ubuntu-latest`.

### «http: server gave HTTP response to HTTPS client» عند docker login

Docker لا يقبل Registry يعمل عبر HTTP إلا إذا [أعلنته «غير آمن»](https://docs.docker.com/reference/cli/dockerd/?ref=arabroot.io#insecure-registries)، والحل الصحيح هو تشغيل Gitea عبر HTTPS، أما في بيئة تجريبية معزولة فقط ومع الـ Image dind فتستطيع أن تمرر إلى Container الـ runner الملف `/etc/docker/daemon.json` وفيه `{"insecure-registries": ["git.example.com:3000"]}`.

### المهمة لا تصل إلى git.example.com

Containers المهام تبحث عن الأسماء عبر DNS المعتاد، فإذا كان النطاق Domain داخلياً فقط فأضف عنوانه في `container.options`، مثل `--add-host=git.example.com:203.0.113.10`، والمفتاح موجود في [مثال الإعداد الرسمي](https://docs.gitea.com/runner/reference/config-example/?ref=arabroot.io).

### «413 Request Entity Too Large» أثناء docker push

سبب هذا الخطأ حد حجم الطلب في الـ Reverse Proxy الذي أمام Gitea، والحل أن ترفع هذا الحد، مثل `client_max_body_size 1024m;` في [Nginx Proxy Manager](https://arabroot.io/articles/nginx-proxy-manager-%D9%86%D8%B7%D8%A7%D9%82%D8%A7%D8%AA-%D9%88%D8%B4%D9%87%D8%A7%D8%AF%D8%A7%D8%AA-tls/)، والسبب أن طبقات Layers الـ Images قد تتجاوز مئات الميغابايتات.

## الخلاصة

وصلنا لنهاية الموضوع، وأهم ما فيه:

- Gitea Actions يبني الكود ويرفع الـ Images على سيرفرك، فلا يخرج الكود ولا الأسرار إلى خدمة خارجية، وملفاته بصيغة GitHub Actions تقريباً.
- Gitea لا ينفذ المهام بنفسه، وإنما يوزعها على Gitea Runner الذي كان اسمه act\_runner، فسجله برمز من Site Administration واحفظ ال Volume الذي فيه ملف `.runner`.
- لا تمرر `docker.sock` الخاص بالسيرفر إلى الـ runner، واستخدم النوع dind، وتذكر أنه يعزل المهام عن خدماتك ولكنه ليس حاجزاً أمام كود خبيث، فلا تشغل عليه إلا كوداً تثق به.
- سجل الدخول في الـ Registry برمز بصلاحية `write:package` وحدها محفوظ في Secrets، وضع ما ليس سرياً في Variables.
- ثبت كل action تجلبها من GitHub على هاش commit، وأضف قواعد تنظيف للحزم حتى لا يمتلئ القرص.

## سجل التحديثات

- سبتمبر 2026: كتابة الدليل واختباره على [Gitea 1.27.3](https://github.com/go-gitea/gitea/releases/tag/v1.27.3?ref=arabroot.io) وGitea Runner 4.0.0.
- أكتوبر 2026: مراجعة الدليل، وتحديث Gitea Runner إلى [الإصدار 4.1.0](https://gitea.com/gitea/runner/releases/tag/v4.1.0?ref=arabroot.io) واختباره مع Gitea 1.27.3 و28.0.0، وتصحيح تاريخ تغيير اسم act\_runner، والاكتفاء بالصلاحية `write:package` للرمز، وإضافة ملاحظات الأمان عن الوضع privileged وطلبات الدمج من Fork وتثبيت ال actions.