# 🔧 راهنمای CI/CD Pipeline — Gitea Actions + K3s > آخرین بروزرسانی: February 17, 2026 --- ## 📐 معماری کلی ``` ┌─────────────────────────────────────────────────────────┐ │ K3s Cluster (194.5.195.53) │ │ │ │ ┌─────────────────────────────────────────────────┐ │ │ │ gitea-runner Pod (2 containers) │ │ │ │ │ │ │ │ ┌──────────────────┐ ┌──────────────────────┐ │ │ │ │ │ docker (DinD) │ │ runner (act_runner) │ │ │ │ │ │ docker:dind │ │ gitea/act_runner │ │ │ │ │ │ privileged: true │ │ DOCKER_HOST= │ │ │ │ │ │ port: 2375 │ │ tcp://localhost:2375│ │ │ │ │ └──────────────────┘ └──────────────────────┘ │ │ │ │ ▲ shared volumes: docker-storage │ │ │ └─────────────────────────────────────────────────┘ │ │ │ │ ┌─────────────┐ ┌───────────┐ ┌────────────────┐ │ │ │ Gitea │ │ Nexus │ │ Docker Reg. │ │ │ │ :3000 │ │ :32081 │ │ :32082 (pull) │ │ │ │ │ │ (NuGet) │ │ :30080 (push) │ │ │ └─────────────┘ └───────────┘ └────────────────┘ │ └─────────────────────────────────────────────────────────┘ ``` ### سه لایه Docker-in-Docker: ``` K3s containerd (لایه ۱) └── gitea-runner Pod → docker container (DinD daemon) (لایه ۲) └── workflow: docker run / docker build (لایه ۳) ``` --- ## 📁 فایل‌های Workflow ### 🐳 K8s Pipelines (Docker + K3s) — آفلاین | سرویس | فایل | Branch | Image | Deploy | |--------|------|--------|-------|--------| | CMS | `kub-deploy.yml` | `kub-stage` | `admin/cms` | SSH → kubectl | | BackOffice | `kub-deploy.yml` | `kub-stage` | `admin/backoffice` | SSH → kubectl | | FrontOffice | `kub-deploy.yml` | `kub-stage` | `admin/frontoffice` | SSH → kubectl | | CMS | `prod-deploy.yml` | `production` | `admin/cms:prod` | SSH → kubectl | | BackOffice | `prod-deploy.yml` | `production` | `admin/backoffice:prod` | SSH → kubectl | | FrontOffice | `prod-deploy.yml` | `production` | `admin/frontoffice:prod` | SSH → kubectl | ### 🪟 Windows/IIS Pipelines (Legacy) — آنلاین | سرویس | فایل | Branch | Target | |--------|------|--------|--------| | CMS | `cms-stage.yml` | `stage_new` | IIS → `cms.kbs1.ir` | | BackOffice | `bo-stage.yml` | `stage-new` | IIS → `admin.kbs1.ir` | | FrontOffice | `fo-stage.yml` | `stage-new` | IIS → `kbs1.ir` | > ⚠️ Stage pipeline ها از Windows runner + IIS استفاده میکنن و Docker ندارن. ### ساختار مشترک Pipeline: ``` 1. Start Docker daemon (DinD) 2. Checkout code (git clone) 3. Login to Docker registries (32082 + 30080) 4. [CMS only] Publish Protobuf packages 5. Build Docker Image 6. Push to Registry 7. Deploy to Kubernetes (SSH → kubectl rollout restart) ``` --- ## 🐛 مشکلات حل‌شده و راه‌حل‌ها ### مشکل ۱: `iptables failed: Permission denied` **خطا:** ``` iptables v1.8.10 (nf_tables): Could not fetch rule set generation id: Permission denied ``` **علت:** K3s containerd به Docker daemon اجازه تغییر iptables نمیده. **راه‌حل:** غیرفعال کردن networking در dockerd: ```bash dockerd --iptables=false --ip6tables=false --bridge=none --storage-driver=vfs & ``` > ⚠️ با `--bridge=none` نیاز به شبکه‌سازی Docker نیست چون فقط build و push انجام میشه. --- ### مشکل ۲: `failed to unmount overlayfs: operation not permitted` **خطا:** ``` failed to register layer: unshare: operation not permitted ``` **علت:** `overlay2` storage driver نیاز به mount namespace داره که داخل K3s مجاز نیست. **راه‌حل:** استفاده از `vfs` storage driver: ```bash dockerd --storage-driver=vfs & ``` > ⚠️ `vfs` کندتره ولی هیچ mount syscall خاصی نیاز نداره. برای CI/CD کافیه. --- ### مشکل ۳: `no basic auth credentials` هنگام pull ایمیج **خطا:** ``` Error response from daemon: Head "https://194.5.195.53:32082/v2/dotnet/sdk/manifests/9.0": no basic auth credentials ``` **علت:** `docker login` فقط قبل از push انجام میشد، ولی `docker build` (یا `docker run`) هم از `32082` ایمیج pull میکنه. **راه‌حل:** اضافه کردن step "Login to Docker registries" بلافاصله بعد از Checkout: ```yaml - name: Login to Docker registries run: | echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login 194.5.195.53:32082 -u admin --password-stdin echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login ${{ env.REGISTRY }} -u admin --password-stdin ``` --- ### مشکل ۴: `unshare: operation not permitted` هنگام extract لایه‌ها **خطا:** ``` docker: failed to register layer: unshare: operation not permitted ``` **علت اصلی (دو بخش):** **بخش ۱:** Gitea act_runner دیفالت `container.privileged: false` داره. یعنی job container ها بدون privileged ساخته میشن — حتی اگه workflow بنویسه `options: --privileged`. **بخش ۲:** env var `CONFIG_FILE` در runner container ست نبود → `run.sh` فلگ `--config` رو به `act_runner daemon` پاس نمیداد → config.yaml اصلاً لود نمیشد! **راه‌حل (سمت سرور):** ۱. ساخت ConfigMap: ```yaml apiVersion: v1 kind: ConfigMap metadata: name: runner-config namespace: default data: config.yaml: | log: level: info runner: file: .runner capacity: 1 timeout: 3h container: privileged: true options: "--security-opt seccomp=unconfined --security-opt apparmor=unconfined" valid_volumes: - "**" ``` ۲. Mount کردن در Deployment + env var: ```bash kubectl patch deployment gitea-runner --type=json -p='[ {"op":"add","path":"/spec/template/spec/containers/1/env/-", "value":{"name":"CONFIG_FILE","value":"/data/config.yaml"}}, {"op":"add","path":"/spec/template/spec/containers/1/volumeMounts/-", "value":{"name":"runner-config","mountPath":"/data/config.yaml","subPath":"config.yaml"}}, {"op":"add","path":"/spec/template/spec/volumes/-", "value":{"name":"runner-config","configMap":{"name":"runner-config"}}} ]' ``` > ⚠️ **نکته مهم:** بدون `CONFIG_FILE=/data/config.yaml` env var، فایل `run.sh` داخل act_runner image فلگ `--config` رو پاس نمیده! --- ### مشکل ۵: Protobuf restore از nuget.org بجای Nexus **علت:** `dotnet restore` بدون `--configfile` از دیفالت NuGet sources استفاده میکنه. **راه‌حل:** ```bash dotnet restore "$proj" --configfile src/NuGet.config ``` --- ### مشکل ۶: عدم دسترسی شبکه با `--bridge=none` **علت:** `dockerd --bridge=none` شبکه Docker bridge رو غیرفعال میکنه. در نتیجه container هایی که با `docker run` یا `docker build` ساخته میشن، دسترسی شبکه ندارن (مثلاً `dotnet restore` نمیتونه به Nexus وصل بشه). **راه‌حل:** استفاده از `--network host` در `docker run` و `docker build`: ```bash # Protobuf step docker run --rm --network host -v $(pwd):/src -w /src ... # Build step DOCKER_BUILDKIT=0 docker build --network host -t ... . ``` --- ### مشکل ۷: `failed to prepare ... as ...: invalid argument` (BuildKit) **خطا:** ``` ERROR: failed to build: failed to solve: failed to prepare xxx as yyy: invalid argument ``` **علت:** BuildKit (بیلدر پیش‌فرض Docker ≥23) از snapshotter overlay استفاده میکنه که با `--storage-driver=vfs` سازگاری نداره. **راه‌حل:** غیرفعال کردن BuildKit: ```bash DOCKER_BUILDKIT=0 docker build --network host -t ... . ``` > ⚠️ Legacy builder از vfs بدون مشکل استفاده میکنه. --- ### مشکل ۸: `COPY libs/` fails in Docker build (BackOffice) **خطا:** ``` COPY failed: file not found in build context: stat libs/: file does not exist ``` **علت:** Dockerfile خط `COPY ["libs/", "libs/"]` داشت ولی `libs/` خارج از Docker build context (`src/`) بود. قبلاً BFF DLLها استفاده می‌شدن، ولی حالا از NuGet package مستقیم استفاده می‌شه. **راه‌حل:** 1. حذف `COPY ["libs/", "libs/"]` از Dockerfile 2. تغییر `ProjectReference` به `PackageReference` در csproj: ```xml ``` --- ### مشکل ۹: ProjectReference خارج از Docker context (FrontOffice/BackOffice) **خطا:** ``` error CS0246: The type or namespace name 'CustomerAddressModel' could not be found ``` **علت:** csproj از `ProjectReference Include="../../../CMS/src/CMSMicroservice.Protobuf/..."` استفاده می‌کرد. در Docker build context فقط `src/` موجوده → CMS قابل دسترسی نیست. **راه‌حل:** 1. بامپ نسخه پروتوباف (`0.0.177` → `0.0.178`) 2. `dotnet pack -c Release` و push به Nexus 3. تغییر هر دو پروژه (FrontOffice + BackOffice) به `PackageReference` ```bash # Pack & Push cd CMS/src/CMSMicroservice.Protobuf dotnet pack -c Release dotnet nuget push bin/Release/Foursat.CMSMicroservice.Protobuf.0.0.178.nupkg \ --source http://194.5.195.53:32081/repository/foursat-nuget-hosted/index.json \ --api-key admin:87zH26nbqT --skip-duplicate ``` --- ### مشکل ۱۰: `nginx:alpine` TLS handshake timeout **خطا:** ``` Get "https://registry-1.docker.io/v2/": net/http: TLS handshake timeout ``` **علت:** Dockerfile خط `FROM nginx:alpine` مستقیم از Docker Hub پول می‌کرد ولی سرور به Docker Hub دسترسی نداره. **راه‌حل:** تغییر به رجیستری لوکال: ```dockerfile # قبل FROM nginx:alpine AS final # بعد FROM 194.5.195.53:32082/nginx:alpine AS final ``` --- ### مشکل ۱۱: SERVER_PASSWORD secret missing → Permission denied **خطا:** ``` Permission denied, please try again. ``` **علت:** سکرت `SERVER_PASSWORD` در ریپو Gitea تنظیم نشده بود. Pipeline از `sshpass -e` با `${{ secrets.SERVER_PASSWORD }}` برای SSH استفاده می‌کنه. **راه‌حل:** اضافه کردن سکرت از طریق Gitea API: ```bash curl -sk -u "admin:87zH26nbqT" -X PUT \ "https://git.se.kbs1.ir/api/v1/repos/admin/BackOffice/actions/secrets/SERVER_PASSWORD" \ -H "Content-Type: application/json" -d '{"data":"87zH26nbqT"}' ``` --- ### مشکل ۱۲: CMS ingress 502 — backend-protocol: GRPC **خطا:** `https://cms.se.kbs1.ir/` → 502 Bad Gateway **علت:** CMS ingress annotation `backend-protocol: GRPC` داشت + Kestrel فقط `Http2`. مرورگر HTTP/1.1 می‌فرسته → nginx نمی‌تونه به gRPC backend فوروارد کنه. **راه‌حل (دو تغییر):** 1. Kestrel protocol → `Http1AndHttp2` (هم gRPC هم REST): ```bash kubectl set env deployment/cms Kestrel__EndpointDefaults__Protocols=Http1AndHttp2 ``` 2. حذف GRPC annotations از ingress: ```bash kubectl annotate ingress cms-ingress nginx.ingress.kubernetes.io/backend-protocol- kubectl annotate ingress cms-ingress nginx.ingress.kubernetes.io/grpc-backend- ``` > ⚠️ FrontOffice از gRPC-Web استفاده می‌کنه که روی HTTP/1.1 هم کار می‌کنه. --- ## 🔄 تغییرات prod-deploy (قدیم → جدید) | مورد | قدیم (prod-deploy) | جدید | |------|-------------------|------| | Container image | `docker:latest` | `docker-sshpass:latest` (شامل sshpass + git) | | Proxy | `HTTP_PROXY` + `HTTPS_PROXY` | حذف شد (آفلاین) | | Registry | `gitea-svc:3000` + external | فقط `194.5.195.53:30080` | | kubectl | `apk add` + `curl` از اینترنت | SSH → `kubectl` مستقیم روی سرور | | Auth | hardcoded password | `secrets.REGISTRY_PASSWORD` + `secrets.SERVER_PASSWORD` | | BuildKit | فعال (دیفالت) | `DOCKER_BUILDKIT=0` | | Network | Docker bridge (دیفالت) | `--network host` | | dockerd | دیفالت | `--iptables=false --ip6tables=false --bridge=none --storage-driver=vfs` | | Deploy | `KUBECONFIG_PROD` (base64) | SSH + sshpass (مثل kub-stage) | > ✅ حالا همه ۶ K8s pipeline (۳ stage + ۳ prod) از **یک الگوی مشترک آفلاین** استفاده میکنن. --- containers: - name: docker # DinD sidecar image: 194.5.195.53:32082/docker:dind securityContext: privileged: true env: - DOCKER_TLS_CERTDIR: "" volumeMounts: - /var/lib/docker → docker-storage - /etc/docker/daemon.json → docker-config (ConfigMap) - name: runner # Gitea act_runner image: 194.5.195.53:32082/gitea/act_runner:latest env: - GITEA_INSTANCE_URL: http://gitea-svc:3000 - DOCKER_HOST: tcp://localhost:2375 - CONFIG_FILE: /data/config.yaml # ← حیاتی! بدون این runner config لود نمیشه volumeMounts: - /data → runner-data - /data/config.yaml → runner-config (ConfigMap) ``` ### ConfigMaps: | نام | محتوا | Mount Path | |-----|-------|------------| | `docker-daemon-config` | `daemon.json` با insecure-registries | `/etc/docker/daemon.json` | | `runner-config` | `config.yaml` با privileged + seccomp | `/data/config.yaml` | ### Labels (ثبت‌شده در Gitea): ``` ubuntu-latest → docker://docker.gitea.com/runner-images:ubuntu-latest ubuntu-24.04 → docker://docker.gitea.com/runner-images:ubuntu-24.04 ubuntu-22.04 → docker://docker.gitea.com/runner-images:ubuntu-22.04 ``` --- ## 🔑 Secrets مورد نیاز (Gitea → Settings → Secrets) | Secret | استفاده | |--------|---------| | `REGISTRY_PASSWORD` | پسورد Docker registry (admin) | | `SERVER_PASSWORD` | پسورد SSH سرور (root) — ⚠️ باید در هر ۳ ریپو ست بشه | > **نکته:** اگر `SERVER_PASSWORD` ست نباشه، مرحله Deploy با `Permission denied` فیل می‌شه. > با API اضافه کنید: > ```bash > curl -sk -u "admin:PASSWORD" -X PUT \ > "https://git.se.kbs1.ir/api/v1/repos/admin/REPO/actions/secrets/SERVER_PASSWORD" \ > -H "Content-Type: application/json" -d '{"data":"PASSWORD"}' > ``` --- ## 🔧 dockerd فلگ‌های نهایی ```bash dockerd --iptables=false --ip6tables=false --bridge=none --storage-driver=vfs & ``` | Flag | دلیل | |------|-------| | `--iptables=false` | K3s اجازه تغییر iptables نمیده | | `--ip6tables=false` | مشابه بالا برای IPv6 | | `--bridge=none` | نیازی به Docker bridge network نیست | | `--storage-driver=vfs` | overlay2 نمیتونه mount کنه داخل K3s | --- ## 🔍 عیب‌یابی Pipeline ### ۱. چک وضعیت Runner: ```bash # SSH به سرور ssh root@194.5.195.53 # آیا runner pod بالاست؟ kubectl get pods -l app=gitea-runner # لاگ runner kubectl logs -c runner --tail=30 # لاگ DinD kubectl logs -c docker --tail=30 ``` ### ۲. تست Docker داخل Runner: ```bash # exec به DinD container kubectl exec -c docker -- docker info # آیا registry قابل دسترسیه؟ kubectl exec -c docker -- docker pull 194.5.195.53:32082/dotnet/sdk:9.0 ``` ### ۳. چک config runner: ```bash # آیا config.yaml mount شده؟ kubectl exec -c runner -- cat /data/config.yaml # آیا privileged فعاله؟ kubectl exec -c docker -- docker inspect \ --format '{{.HostConfig.Privileged}} {{.HostConfig.SecurityOpt}}' ``` ### ۴. ری‌استارت runner: ```bash kubectl rollout restart deployment/gitea-runner kubectl rollout status deployment/gitea-runner --timeout=120s ``` --- ## 📋 Workflow Template (کامل) ```yaml name: Build and Deploy to Kubernetes on: push: branches: - kub-stage env: REGISTRY: 194.5.195.53:30080 IMAGE_NAME: admin/ K8S_SERVER: 194.5.195.53 jobs: build-and-deploy: runs-on: ubuntu-latest container: image: 194.5.195.53:32082/docker-sshpass:latest options: --privileged steps: - name: Start Docker daemon run: | mkdir -p /etc/docker cat > /etc/docker/daemon.json << 'DAEMON' { "insecure-registries": ["194.5.195.53:30080", "194.5.195.53:32500", "194.5.195.53:32082"] } DAEMON dockerd --iptables=false --ip6tables=false --bridge=none --storage-driver=vfs & for i in $(seq 1 90); do if docker info >/dev/null 2>&1; then echo "✅ Docker ready"; break fi sleep 2 done - name: Checkout code run: | git clone --depth 1 --branch kub-stage http://gitea-svc:3000/admin/.git . - name: Login to Docker registries run: | echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login 194.5.195.53:32082 -u admin --password-stdin echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login ${{ env.REGISTRY }} -u admin --password-stdin - name: Build Docker Image run: | DOCKER_BUILDKIT=0 docker build --network host -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest . - name: Push to Registry run: | docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest - name: Deploy to Kubernetes run: | export SSHPASS="${{ secrets.SERVER_PASSWORD }}" sshpass -e ssh -o StrictHostKeyChecking=no root@${{ env.K8S_SERVER }} " kubectl rollout restart deployment/ kubectl rollout status deployment/ --timeout=180s " ``` --- ## 🐛 باگ بحرانی: Cross-Deployment — Push به Production ری‌دیپلوی Staging (اسفند ۱۴۰۴) ### علائم: - Push به برنچ `production` → هم production و هم staging ری‌دیپلوی شدند - CMS staging pod بعد از push ریستارت شد - Runner log: **۲ تسک CMS** بجای ۱ تسک اجرا شد ### علت ریشه‌ای: Gitea Act Runner **تمام فایل‌های workflow** داخل `.gitea/workflows/` برنچ push شده رو اجرا می‌کنه — حتی اگه `on.push.branches` برنچ دیگه‌ای باشه. وقتی production push شد، `kub-deploy.yml` (trigger: `kub-stage`) هم اجرا شد و ایمیج `admin/cms:latest` رو با کد production ساخت → staging از `latest` pull کرد → **staging با DB production بالا اومد!** ### راه‌حل: حذف workflow‌های staging از برنچ production (هر ۳ ریپو): ```bash git rm .gitea/workflows/kub-deploy.yml .gitea/workflows/cms-stage.yml # CMS git rm .gitea/workflows/fo-stage.yml .gitea/workflows/kub-deploy.yml # FrontOffice git rm .gitea/workflows/bo-stage.yml .gitea/workflows/kub-deploy.yml # BackOffice ``` > ⚠️ **قانون طلایی:** هر برنچ فقط workflow مربوط به خودش رو داشته باشه. --- ## 🐛 مشکل ۱۳: Production deploy ایمیج pull نمی‌شد **علت:** Production K8s از `git.foursat.afrino.co/admin/cms:prod` pull می‌کرد، ولی CI ایمیج رو به `194.5.195.53:30080` push می‌کرد. **راه‌حل:** 1. اضافه کردن `194.5.195.53:30080` به `/etc/rancher/k3s/registries.yaml` پروداکشن + ری‌استارت K3s 2. آپدیت deployment image: `kubectl set image deployment/cms cms=194.5.195.53:30080/admin/cms:prod` 3. فیکس `prod-deploy.yml`: `K8S_SSH_PASSWORD` → `SERVER_PASSWORD`, `rollout restart` → `set image :sha` --- ## 🔄 Production Workflow Template (فعلی) ```yaml name: Build and Deploy to Production on: push: branches: [production] env: REGISTRY: 194.5.195.53:30080 IMAGE_NAME: admin/ K8S_SERVER: 45.149.79.127 jobs: build-and-deploy: runs-on: ubuntu-latest container: image: 194.5.195.53:32082/docker-sshpass:latest options: --privileged steps: # ... (Start Docker, Checkout, Login — مشابه staging) - name: Build Docker Image run: | DOCKER_BUILDKIT=0 docker build --network host \ -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} \ -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:prod . - name: Push to Registry run: | docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:prod - name: Deploy to Production run: | export SSHPASS="${{ secrets.SERVER_PASSWORD }}" sshpass -e ssh -o StrictHostKeyChecking=no root@${{ env.K8S_SERVER }} " kubectl set image deployment/ =${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} kubectl rollout status deployment/ --timeout=300s " ``` > تفاوت staging vs production: staging = tag `latest` + `rollout restart` | production = tag `sha` + `set image` --- ## 🏗️ مشخصات دو محیط | | Staging | Production | |--|---------|------------| | **سرور** | `194.5.195.53` | `45.149.79.127` | | **DB** | `mssql-svc@Foursat` | `45.149.79.127,31433@KBS` | | **Registry** | `194.5.195.53:30080` (local) | همان staging registry | | **Image Tags** | `:latest` | `:prod` + `:sha` | | **Branch** | `kub-stage` | `production` | | **Domains** | `*.se.kbs1.ir` | `*.kbs1.ir` | --- ## 🐛 مشکل ۱۴: K8S_SERVER اشتباه در prod-deploy.yml (CMS + BackOffice) **تاریخ:** February 17, 2026 **علت:** `K8S_SERVER` در `prod-deploy.yml` CMS و BackOffice هنوز `194.5.195.53` (staging) بود بجای `45.149.79.127` (production). **عارضه:** `kubectl set image` به سرور staging ارسال می‌شد — deployment production آپدیت نمی‌شد. **فایل‌های فیکس شده:** - `CMS/.gitea/workflows/prod-deploy.yml` — `K8S_SERVER: 194.5.195.53` → `45.149.79.127` - `BackOffice/.gitea/workflows/prod-deploy.yml` — `K8S_SERVER: 194.5.195.53` → `45.149.79.127` - FrontOffice قبلاً درست بود ✅ **فیکس اضافی:** هر دو workflow از `kubectl rollout restart` به `kubectl set image` تغییر کردن تا ایمیج SHA-tagged واقعاً set بشه. --- ## 🐛 مشکل ۱۵: نبود appsettings.Production.json — URLهای staging روی production **تاریخ:** February 17, 2026 **علت:** هیچکدوم از ۳ پروژه `appsettings.Production.json` نداشتن. از طرفی `ASPNETCORE_ENVIRONMENT=Production` ست بود → fallback به `appsettings.json` (که URLهای staging داشت). **عارضه‌ها:** - CMS: `CmsBaseUrl=cms.se.kbs1.ir` → ZarinPal callback به staging برمی‌گشت - CMS: `FrontOfficeBaseUrl=foursat.se.kbs1.ir` → redirect بعد از پرداخت به staging می‌رفت - FrontOffice: `GwUrl=localhost:32846` → gRPC به هیچ‌جا وصل نمی‌شد - BackOffice: `GwUrl=localhost:32847` → gRPC به هیچ‌جا وصل نمی‌شد **فایل‌های ساخته شده:** | پروژه | فایل | محتوای کلیدی | |--------|------|-------------| | CMS | `src/CMSMicroservice.WebApi/appsettings.Production.json` | `CmsBaseUrl=cms.kbs1.ir`, `FrontOfficeBaseUrl=kbs1.ir`, `DB=KBS`, `ZarinPal.UseSandbox=false` | | FrontOffice | `src/FrontOffice.Main/appsettings.Production.json` | `GwUrl=cms.kbs1.ir` | | BackOffice | `src/BackOffice/wwwroot/appsettings.Production.json` | `GwUrl=cms.kbs1.ir` | > ⚠️ **نکته:** CMS فایل `appsettings.Production.json` در `.gitignore` هست (`**/ appsettings.Production.json`). با `git add -f` ترک شد. بعد از هر تغییر باید دوباره force add بشه. --- ## 🐛 مشکل ۱۶: nginx image path اشتباه در BackOffice Dockerfile (production branch) **تاریخ:** February 17, 2026 **علت:** Dockerfile روی برنچ `production` از `194.5.195.53:32082/library/nginx:alpine` استفاده می‌کرد که در رجیستری وجود نداشت. روی `kub-stage` قبلاً فیکس شده بود ولی merge به production این خط رو override کرده بود. **ارور CI:** ``` Step 9/14 : FROM 194.5.195.53:32082/library/nginx:alpine AS final manifest for 194.5.195.53:32082/library/nginx:alpine not found: manifest unknown ``` **رفع:** ```dockerfile # قبل (اشتباه) FROM 194.5.195.53:32082/library/nginx:alpine AS final # بعد (صحیح) FROM 194.5.195.53:32082/nginx:alpine AS final ``` **نکته:** این فیکس مستقیماً روی برنچ `production` انجام و push شد (کامیت `743403e`).