DevOps Labs
← Назад к списку

CI/CD на GitHub Actions

CI/CD на GitHub Actions

Цель: настроить полный цикл CI/CD для приложения, лежащего на GitHub.
Сначала — деплой на один сервер по SSH, потом — масштабирование на несколько.
Всё расписано максимально подробно, с подсказками и примерами.


🧭 0. Общая картина (что мы строим)

  1. CI (Continuous Integration)
    Проверка кода на каждом push/PR: сборка, тесты, линтеры.
  2. CD (Continuous Delivery / Deployment)
    Автоматический деплой на сервер при изменениях в main.
  3. Безопасность и воспроизводимость
    Секреты в GitHub Secrets, доступ по SSH-ключу, откаты и логи.

⚙️ 1. Предварительные требования

1.1 GitHub репозиторий

1.2 Сервер

1.3 Способ деплоя

Вариант A: rsync + systemd (просто, но ручнее)
Вариант B: Docker + docker-compose (современно, легко масштабируется)
➡️ Начни с варианта B — он лучше подходит для CI/CD.


🧑‍💻 2. Подготовка сервера

2.1 Создать пользователя и добавить SSH-ключ

adduser deploy
usermod -aG sudo deploy
su - deploy
mkdir -p ~/.ssh && chmod 700 ~/.ssh
nano ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys

Создай ключ для GitHub Actions:

ssh-keygen -t ed25519 -C "github-actions" -f ./github_actions_ed25519

2.2 Установить Docker и docker-compose

sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \
  sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
sudo usermod -aG docker deploy
sudo systemctl enable docker --now

Проверка:

docker version
docker compose version

2.3 Подготовить директорию приложения

su - deploy
mkdir -p ~/apps/myapp
cd ~/apps/myapp

Сюда положим docker-compose.yml.


🔐 3. Добавление секретов в GitHub

Открой Repository → Settings → Secrets and variables → Actions

Добавь:

Имя Описание
SSH_HOST IP сервера
SSH_USER deploy
SSH_PORT 22
SSH_PRIVATE_KEY содержимое приватного ключа
REGISTRY например ghcr.io/<user>
REGISTRY_USERNAME логин от реестра
REGISTRY_PASSWORD пароль или токен

💡 Все пароли и ключи хранятся только в Secrets, не в коде!


📂 4. Структура проекта

myapp/
├─ .github/
│  └─ workflows/
│     └─ ci-cd.yml
├─ docker-compose.yml
├─ Dockerfile
└─ (исходники приложения)

Пример Dockerfile (Node.js)

FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY --from=build /app/dist ./dist
EXPOSE 3000
CMD ["node", "dist/index.js"]

Пример docker-compose.yml

version: "3.9"
services:
  web:
    image: ghcr.io/yourorg/myapp:latest
    restart: always
    env_file:
      - .env
    ports:
      - "80:3000"

✅ 5. Настройка CI (Continuous Integration)

Файл: .github/workflows/ci-cd.yml

name: CI-CD

on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]

jobs:
  ci:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: "20"
          cache: "npm"

      - name: Install dependencies
        run: npm ci

      - name: Lint
        run: npm run lint --if-present

      - name: Test
        run: npm test --if-present

      - name: Build
        run: npm run build --if-present

🚀 6. CD (Continuous Deployment)

Добавь в тот же ci-cd.yml:

  cd:
    needs: [ci]
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Log in to Docker registry
        uses: docker/login-action@v3
        with:
          registry: ${{ secrets.REGISTRY }}
          username: ${{ secrets.REGISTRY_USERNAME }}
          password: ${{ secrets.REGISTRY_PASSWORD }}

      - name: Build & push Docker image
        uses: docker/build-push-action@v6
        with:
          context: .
          file: ./Dockerfile
          push: true
          tags: |
            ${{ secrets.REGISTRY }}/myapp:latest
            ${{ secrets.REGISTRY }}/myapp:${{ github.sha }}

      - name: Prepare SSH
        run: |
          mkdir -p ~/.ssh
          echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519
          chmod 600 ~/.ssh/id_ed25519
          ssh-keyscan -p ${{ secrets.SSH_PORT }} ${{ secrets.SSH_HOST }} >> ~/.ssh/known_hosts

      - name: Deploy to server
        run: |
          ssh -p ${{ secrets.SSH_PORT }} ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} \
            "cd ~/apps/myapp && \
             docker login ${{ secrets.REGISTRY }} -u ${{ secrets.REGISTRY_USERNAME }} -p ${{ secrets.REGISTRY_PASSWORD }} && \
             docker compose pull && \
             docker compose up -d && \
             docker image prune -f"

🧠 7. Что происходит при деплое

  1. GitHub Actions подключается по SSH к серверу.
  2. Логинится в Docker Registry.
  3. Тянет новый образ.
  4. Перезапускает контейнеры.
  5. Удаляет старые, неиспользуемые образы.

✅ В итоге, каждое изменение в main автоматически обновляет сервер.


📈 8. Масштабирование на несколько серверов

Добавь matrix в job:

strategy:
  fail-fast: false
  matrix:
    server:
      - { host: "203.0.113.10", port: "22" }
      - { host: "203.0.113.11", port: "22" }

и замени команду деплоя:

ssh -p ${{ matrix.server.port }} ${{ secrets.SSH_USER }}@${{ matrix.server.host }} ...

Так один job задеплоит приложение на все сервера.


🔍 9. Частые ошибки

Проблема Решение
Permission denied Проверь права ключа (chmod 600) и пользователя deploy
docker compose not found Используй docker-compose или установи плагин
Образ не обновляется Используй тег ${{ github.sha }}
Secrets не видны Деплой разрешён только из main

🧾 10. Контрольный чек-лист


🚧 11. Что улучшить потом


📘 12. Минимальный рабочий пример (целиком)

name: CI-CD

on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]

jobs:
  ci:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: "20"
          cache: "npm"
      - run: npm ci
      - run: npm test --if-present
      - run: npm run build --if-present

  cd:
    needs: [ci]
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: docker/login-action@v3
        with:
          registry: ${{ secrets.REGISTRY }}
          username: ${{ secrets.REGISTRY_USERNAME }}
          password: ${{ secrets.REGISTRY_PASSWORD }}
      - uses: docker/build-push-action@v6
        with:
          context: .
          file: ./Dockerfile
          push: true
          tags: |
            ${{ secrets.REGISTRY }}/myapp:latest
            ${{ secrets.REGISTRY }}/myapp:${{ github.sha }}
      - name: Prepare SSH
        run: |
          mkdir -p ~/.ssh
          echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519
          chmod 600 ~/.ssh/id_ed25519
          ssh-keyscan -p ${{ secrets.SSH_PORT }} ${{ secrets.SSH_HOST }} >> ~/.ssh/known_hosts
      - name: Deploy
        run: |
          ssh -p ${{ secrets.SSH_PORT }} ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} \
            "cd ~/apps/myapp && \
             docker login ${{ secrets.REGISTRY }} -u ${{ secrets.REGISTRY_USERNAME }} -p ${{ secrets.REGISTRY_PASSWORD }} && \
             docker compose pull && \
             docker compose up -d && \
             docker image prune -f"

✅ Итог

Теперь ты можешь:

💬 Если нужно — можно добавить шаблон для Python, Go или Ruby-приложений в этом же формате.


📚 Дополнительные ресурсы


Версия документа: 1.0
Дата последнего обновления: 26 октября 2025
Автор: DevOps Community