🚀 آموزش کامل CI/CD برای پروژه .NET

با Docker، GitHub Actions و سرور Ubuntu - از صفر تا استقرار کامل

📖 مقدمه و معرفی CI/CD

CI/CD چیست؟

CI (Continuous Integration) یا یکپارچه‌سازی مداوم، به فرآیندی گفته می‌شود که در آن تغییرات کد به صورت مداوم با کد اصلی ادغام شده و تست‌های خودکار روی آن اجرا می‌شود.

CD (Continuous Deployment/Delivery) یا استقرار مداوم، به فرآیندی گفته می‌شود که در آن کد پس از موفقیت در تست‌ها، به صورت خودکار روی سرور مستقر می‌شود.

Developer Push
GitHub Actions
Build & Test
Docker Image
Deploy to Server

چرا Docker؟

⚙️ پیش‌نیازها

نرم‌افزارهای مورد نیاز روی سیستم محلی:

موارد مورد نیاز آنلاین:

💡 نکته: برای سرور VPS می‌توانید از سرویس‌دهندگان ایرانی مانند ابرآروان، پارس‌پک یا سرویس‌های خارجی مانند DigitalOcean، Linode استفاده کنید.

۱ ایجاد پروژه .NET

ابتدا یک پروژه Web API ساده با .NET ایجاد می‌کنیم.

ایجاد پوشه پروژه و راه‌اندازی:

# ایجاد پوشه پروژه
mkdir MyDotNetApp
cd MyDotNetApp

# ایجاد پروژه Web API
dotnet new webapi -n MyDotNetApp

# ورود به پوشه پروژه
cd MyDotNetApp

# اجرای تست محلی
dotnet run
✅ بررسی: پس از اجرا، آدرس http://localhost:5000/weatherforecast را در مرورگر باز کنید. باید یک JSON با اطلاعات آب و هوا ببینید.

ساختار پوشه پروژه:

MyDotNetApp/
├── MyDotNetApp/
│   ├── Controllers/
│   │   └── WeatherForecastController.cs
│   ├── Properties/
│   │   └── launchSettings.json
│   ├── appsettings.json
│   ├── appsettings.Development.json
│   ├── Program.cs
│   └── MyDotNetApp.csproj
└── (Dockerfile و docker-compose.yml اینجا اضافه می‌شوند)

۲ ایجاد Dockerfile

Dockerfile فایلی است که دستورات ساخت Docker Image را مشخص می‌کند. ما از روش Multi-Stage Build استفاده می‌کنیم که باعث کاهش حجم Image نهایی می‌شود.

توضیح Multi-Stage Build:

Dockerfile
# ===========================================
# مرحله ۱: Build
# ===========================================
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src

# کپی فایل پروژه و restore وابستگی‌ها
COPY ["MyDotNetApp/MyDotNetApp.csproj", "MyDotNetApp/"]
RUN dotnet restore "MyDotNetApp/MyDotNetApp.csproj"

# کپی بقیه فایل‌ها
COPY . .
WORKDIR "/src/MyDotNetApp"

# Build پروژه
RUN dotnet build "MyDotNetApp.csproj" -c Release -o /app/build

# ===========================================
# مرحله ۲: Publish
# ===========================================
FROM build AS publish
RUN dotnet publish "MyDotNetApp.csproj" -c Release -o /app/publish /p:UseAppHost=false

# ===========================================
# مرحله ۳: Runtime (Image نهایی)
# ===========================================
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS final
WORKDIR /app

# تنظیم پورت
EXPOSE 8080
ENV ASPNETCORE_URLS=http://+:8080

# کپی فایل‌های publish شده
COPY --from=publish /app/publish .

# اجرای برنامه
ENTRYPOINT ["dotnet", "MyDotNetApp.dll"]

توضیح هر بخش:

دستور توضیح
FROM تعیین Image پایه
WORKDIR تعیین پوشه کاری داخل Container
COPY کپی فایل‌ها از سیستم میزبان به Container
RUN اجرای دستورات در زمان Build
EXPOSE مشخص کردن پورت (برای مستندسازی)
ENTRYPOINT دستور اجرا هنگام شروع Container

ایجاد فایل .dockerignore:

این فایل مشخص می‌کند چه فایل‌هایی نباید به Docker Context ارسال شوند:

.dockerignore
**/.git
**/.vs
**/.vscode
**/bin
**/obj
**/.dockerignore
**/Dockerfile*
**/docker-compose*
**/*.md
**/.gitignore

تست Build محلی:

# در پوشه ریشه پروژه (کنار Dockerfile)
docker build -t mydotnetapp:latest .

# اجرای Container
docker run -d -p 8080:8080 --name myapp mydotnetapp:latest

# تست
curl http://localhost:8080/weatherforecast

۳ ایجاد Docker Compose

Docker Compose ابزاری است برای تعریف و اجرای چند Container به صورت همزمان. حتی اگر فعلاً یک Container دارید، استفاده از آن مدیریت را ساده‌تر می‌کند.

docker-compose.yml
version: '3.8'

services:
  webapi:
    image: ${DOCKER_USERNAME}/mydotnetapp:${TAG:-latest}
    container_name: mydotnetapp
    ports:
      - "8080:8080"
    environment:
      - ASPNETCORE_ENVIRONMENT=Production
      - ASPNETCORE_URLS=http://+:8080
    restart: unless-stopped
    networks:
      - app-network
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

networks:
  app-network:
    driver: bridge

توضیح پارامترها:

مثال پیشرفته با دیتابیس:

اگر پروژه شما به دیتابیس نیاز دارد:

docker-compose.yml (با PostgreSQL)
version: '3.8'

services:
  webapi:
    image: ${DOCKER_USERNAME}/mydotnetapp:${TAG:-latest}
    container_name: mydotnetapp
    ports:
      - "8080:8080"
    environment:
      - ASPNETCORE_ENVIRONMENT=Production
      - ASPNETCORE_URLS=http://+:8080
      - ConnectionStrings__DefaultConnection=Host=db;Database=mydb;Username=postgres;Password=${DB_PASSWORD}
    depends_on:
      db:
        condition: service_healthy
    restart: unless-stopped
    networks:
      - app-network

  db:
    image: postgres:16-alpine
    container_name: postgres_db
    environment:
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=${DB_PASSWORD}
      - POSTGRES_DB=mydb
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped
    networks:
      - app-network

volumes:
  postgres_data:

networks:
  app-network:
    driver: bridge

۴ آپلود به GitHub

ایجاد Repository جدید:

  1. به github.com/new بروید
  2. نام Repository را وارد کنید (مثلاً MyDotNetApp)
  3. گزینه Private یا Public را انتخاب کنید
  4. روی Create repository کلیک کنید

Push کردن کد:

# در پوشه ریشه پروژه
git init
git add .
git commit -m "Initial commit - .NET project with Docker"

# اتصال به GitHub
git remote add origin https://github.com/YOUR_USERNAME/MyDotNetApp.git
git branch -M main
git push -u origin main

ساختار نهایی Repository:

MyDotNetApp/
├── .github/
│   └── workflows/
│       └── deploy.yml          # فایل CI/CD
├── MyDotNetApp/
│   ├── Controllers/
│   ├── Program.cs
│   └── MyDotNetApp.csproj
├── .dockerignore
├── .gitignore
├── Dockerfile
├── docker-compose.yml
└── README.md

۵ راه‌اندازی سرور Ubuntu

۵.۱ اتصال به سرور:

# اتصال SSH
ssh root@YOUR_SERVER_IP

# یا با کلید
ssh -i ~/.ssh/your_key root@YOUR_SERVER_IP

۵.۲ به‌روزرسانی سیستم:

# آپدیت پکیج‌ها
sudo apt update && sudo apt upgrade -y

# نصب ابزارهای ضروری
sudo apt install -y curl wget git apt-transport-https ca-certificates gnupg lsb-release

۵.۳ نصب Docker:

# اضافه کردن GPG key رسمی Docker
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg

# اضافه کردن Repository
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# نصب Docker
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin

# اضافه کردن کاربر به گروه docker
sudo usermod -aG docker $USER

# فعال‌سازی سرویس
sudo systemctl enable docker
sudo systemctl start docker

# تست نصب
docker --version
docker compose version
⚠️ مهم: پس از اجرای دستور usermod، باید از سرور خارج شده و دوباره SSH بزنید تا تغییرات اعمال شود.

۵.۴ ایجاد پوشه برنامه:

# ایجاد پوشه
sudo mkdir -p /opt/apps/mydotnetapp
sudo chown -R $USER:$USER /opt/apps/mydotnetapp

# ایجاد فایل docker-compose.yml
nano /opt/apps/mydotnetapp/docker-compose.yml

محتوای فایل docker-compose.yml را از مرحله ۳ کپی کنید.

۵.۵ تنظیم Firewall:

# فعال‌سازی UFW
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 8080/tcp
sudo ufw enable

# مشاهده وضعیت
sudo ufw status

۵.۶ ایجاد کلید SSH برای GitHub Actions:

# روی سرور، کلید جدید بسازید
ssh-keygen -t ed25519 -C "github-actions" -f ~/.ssh/github_actions -N ""

# کلید عمومی را به authorized_keys اضافه کنید
cat ~/.ssh/github_actions.pub >> ~/.ssh/authorized_keys

# کلید خصوصی را کپی کنید (برای GitHub Secrets)
cat ~/.ssh/github_actions
💡 نکته: کلید خصوصی را در جای امنی ذخیره کنید. این کلید در مرحله بعد به GitHub Secrets اضافه می‌شود.

۶ ایجاد GitHub Actions Workflow

GitHub Actions سرویس CI/CD رایگان GitHub است. ما یک Workflow می‌سازیم که با هر Push به branch اصلی:

  1. کد را Build می‌کند
  2. Image را به Docker Hub می‌فرستد
  3. به سرور SSH زده و Container را آپدیت می‌کند
.github/workflows/deploy.yml
name: Build and Deploy

# زمان اجرای Workflow
on:
  push:
    branches:
      - main
  workflow_dispatch:  # اجرای دستی

# متغیرهای محیطی
env:
  DOCKER_IMAGE: ${{ secrets.DOCKER_USERNAME }}/mydotnetapp
  DEPLOY_PATH: /opt/apps/mydotnetapp

jobs:
  # ======================================
  # Job 1: Build و Push به Docker Hub
  # ======================================
  build:
    name: Build & Push Docker Image
    runs-on: ubuntu-latest
    
    outputs:
      image_tag: ${{ steps.meta.outputs.tags }}
    
    steps:
      # گام ۱: دریافت کد
      - name: Checkout code
        uses: actions/checkout@v4

      # گام ۲: تنظیم Docker Buildx
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      # گام ۳: ورود به Docker Hub
      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}

      # گام ۴: تولید Tag‌ها
      - name: Docker Metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.DOCKER_IMAGE }}
          tags: |
            type=sha,prefix=
            type=raw,value=latest

      # گام ۵: Build و Push
      - name: Build and Push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

  # ======================================
  # Job 2: استقرار روی سرور
  # ======================================
  deploy:
    name: Deploy to Server
    runs-on: ubuntu-latest
    needs: build  # منتظر اتمام build می‌ماند
    
    steps:
      # گام ۱: اتصال SSH و استقرار
      - name: Deploy via SSH
        uses: appleboy/ssh-action@v1.0.3
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USERNAME }}
          key: ${{ secrets.SERVER_SSH_KEY }}
          port: ${{ secrets.SERVER_PORT || 22 }}
          script: |
            cd ${{ env.DEPLOY_PATH }}
            
            # تنظیم متغیرهای محیطی
            export DOCKER_USERNAME=${{ secrets.DOCKER_USERNAME }}
            export TAG=latest
            
            # Pull کردن Image جدید
            docker compose pull
            
            # متوقف کردن Container قبلی و اجرای جدید
            docker compose up -d --remove-orphans
            
            # پاکسازی Image‌های قدیمی
            docker image prune -f
            
            # نمایش وضعیت
            docker compose ps

توضیح Workflow:

بخش on:

بخش jobs:

۷ تنظیم Secrets در GitHub

Secrets متغیرهای رمزنگاری‌شده‌ای هستند که اطلاعات حساس مانند رمز عبور را ذخیره می‌کنند.

نحوه اضافه کردن Secret:

  1. به صفحه Repository در GitHub بروید
  2. روی Settings کلیک کنید
  3. از منوی سمت چپ، Secrets and variablesActions را انتخاب کنید
  4. روی New repository secret کلیک کنید

لیست Secrets مورد نیاز:

نام Secret توضیح مثال
DOCKER_USERNAME نام کاربری Docker Hub myusername
DOCKER_PASSWORD Access Token یا رمز Docker Hub dckr_pat_xxx...
SERVER_HOST آدرس IP سرور 192.168.1.100
SERVER_USERNAME نام کاربری SSH root یا deploy
SERVER_SSH_KEY کلید خصوصی SSH (کل محتوا) -----BEGIN OPENSSH PRIVATE KEY-----...
SERVER_PORT پورت SSH (اختیاری) 22
⚠️ امنیت: به جای رمز عبور Docker Hub، یک Access Token بسازید:
  1. به Docker Hub Security Settings بروید
  2. روی New Access Token کلیک کنید
  3. دسترسی Read & Write بدهید

۸ تست و استقرار نهایی

۸.۱ اجرای اولیه Workflow:

  1. یک تغییر کوچک در کد ایجاد کنید
  2. Commit و Push کنید:
git add .
git commit -m "Trigger CI/CD pipeline"
git push origin main

۸.۲ مشاهده پیشرفت:

  1. به Repository در GitHub بروید
  2. تب Actions را باز کنید
  3. Workflow در حال اجرا را ببینید
🔄 Build
✅ Deploy
🎉 Live!

۸.۳ بررسی سرور:

# اتصال به سرور
ssh user@YOUR_SERVER_IP

# مشاهده Container‌ها
docker ps

# مشاهده لاگ‌ها
docker logs mydotnetapp -f

# تست API
curl http://localhost:8080/weatherforecast

۸.۴ تست از خارج سرور:

# در مرورگر یا ترمینال محلی
curl http://YOUR_SERVER_IP:8080/weatherforecast
🎉 تبریک! اگر پاسخ JSON دریافت کردید، پایپ‌لاین CI/CD شما با موفقیت کار می‌کند!

🔧 رفع مشکلات رایج

مشکل ۱: خطای احراز هویت Docker Hub

# بررسی کنید:
# 1. DOCKER_USERNAME درست باشد
# 2. DOCKER_PASSWORD یک Access Token معتبر باشد
# 3. Repository در Docker Hub وجود داشته باشد یا Public باشد

مشکل ۲: خطای SSH Connection

# بررسی کنید:
# 1. SERVER_HOST آیپی صحیح باشد
# 2. SERVER_SSH_KEY کل کلید خصوصی باشد (همراه با -----BEGIN و -----END)
# 3. پورت 22 باز باشد
# 4. کلید عمومی در authorized_keys سرور باشد

مشکل ۳: Container راه‌اندازی نمی‌شود

# روی سرور:
docker logs mydotnetapp

# بررسی فضای دیسک
df -h

# بررسی حافظه
free -m

مشکل ۴: پورت در دسترس نیست

# بررسی firewall
sudo ufw status

# باز کردن پورت
sudo ufw allow 8080/tcp

دستورات مفید Docker:

# مشاهده همه Container‌ها
docker ps -a

# مشاهده لاگ‌ها
docker logs CONTAINER_NAME -f --tail 100

# ورود به Container
docker exec -it CONTAINER_NAME /bin/bash

# پاکسازی کامل
docker system prune -a

# ری‌استارت Container
docker compose restart

# توقف و حذف
docker compose down

📋 خلاصه مراحل

۱. پروژه .NET

ایجاد پروژه Web API

۲. Dockerfile

Multi-stage build برای بهینه‌سازی

۳. Docker Compose

تعریف سرویس‌ها و شبکه

۴. GitHub

آپلود کد به Repository

۵. سرور Ubuntu

نصب Docker و تنظیمات SSH

۶. GitHub Actions

Workflow برای CI/CD

۷. Secrets

تنظیم اطلاعات امن

۸. تست

بررسی عملکرد پایپ‌لاین

بازگشت فهرست مطالب