NeveTime Wiki

Автоматизируем деплой: CI/CD пайплайн на GitHub Actions с Docker

NA

NeveTime Administrator

26 мая 2026 г.

Ручной деплой — прошлый век. Современная разработка требует, чтобы каждое изменение проходило автоматическую сборку, тесты и доставку. В этой статье построим полноценный CI/CD с GitHub Actions и Docker, не углубляясь в дебри Kubernetes, но достаточно для реального продакшена.

Что мы автоматизируем

Возьмём типовое веб-приложение на Node.js. Задача:

  • При пуше в ветку main или создании Pull Request собирать Docker-образ.
  • Прогонять линтер и тесты внутри контейнера.
  • Пушить образ в Docker Hub (или GitHub Container Registry).
  • Деплоить на сервер по SSH (или уведомлять о готовности).

Структура проекта

Допустим, у нас есть простой Dockerfile и тесты:

.
├── .github/
│   └── workflows/
│       └── deploy.yml
├── src/
├── Dockerfile
├── docker-compose.yml
└── package.json

Шаг 1: базовый workflow

Создаём .github/workflows/deploy.yml:

name: Build and Deploy

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

jobs:
  build-and-test:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v3

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v2

      - name: Build Docker image
        uses: docker/build-push-action@v4
        with:
          context: .
          load: true          # загружаем образ в локальный Docker
          tags: myapp:ci-${{ github.sha }}

      - name: Run tests inside container
        run: docker run --rm myapp:ci-${{ github.sha }} npm test

Здесь мы собираем образ и сразу запускаем в нём тесты. load: true гарантирует, что образ будет доступен для docker run на раннере.

Шаг 2: добавление линтинга и сканирования безопасности

Перед тестами полезно прогнать линтер и проверить образ на уязвимости. Добавим перед Run tests:

      - name: Lint code (можно внутри контейнера)
        run: docker run --rm myapp:ci-${{ github.sha }} npm run lint

      - name: Scan image for vulnerabilities
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: myapp:ci-${{ github.sha }}
          format: 'table'
          exit-code: '1'
          severity: 'CRITICAL'

Trivy — мощный сканер уязвимостей. При обнаружении критических уязвимостей пайплайн упадёт.

Шаг 3: пуш образа в регистр

Добавим шаги для пуша в Docker Hub. Нужно будет задать секреты DOCKER_USERNAME и DOCKER_PASSWORD в настройках репозитория.

      - name: Login to Docker Hub
        uses: docker/login-action@v2
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}

      - name: Push image
        uses: docker/build-push-action@v4
        with:
          context: .
          push: true
          tags: |
            myorg/myapp:latest
            myorg/myapp:${{ github.sha }}

Теперь образ с уникальным тегом по хешу коммита будет залит в регистр.

Шаг 4: автоматический деплой на сервер

Если используется простой VPS, можно деплоить через SSH, используя appleboy/ssh-action:

  deploy:
    needs: build-and-test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'  # деплоим только из main
    steps:
      - name: Deploy to server
        uses: appleboy/ssh-action@v0.1.10
        with:
          host: ${{ secrets.SSH_HOST }}
          username: ${{ secrets.SSH_USER }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          script: |
            docker pull myorg/myapp:latest
            docker-compose down && docker-compose up -d

Важно: docker-compose.yml на сервере уже должен быть настроен. Тогда после пуша нового образа приложение обновится.

Итоговый workflow (полный)

name: CI/CD Pipeline

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

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: docker/setup-buildx-action@v2
      - name: Build image
        uses: docker/build-push-action@v4
        with:
          context: .
          load: true
          tags: myapp:ci

      - name: Lint
        run: docker run myapp:ci npm run lint

      - name: Test
        run: docker run myapp:ci npm test

      - name: Scan
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: myapp:ci
          format: 'table'
          severity: 'CRITICAL'

      - name: Login to Docker Hub
        uses: docker/login-action@v2
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}

      - name: Push
        uses: docker/build-push-action@v4
        with:
          context: .
          push: true
          tags: myorg/myapp:${{ github.sha }}

  deploy:
    needs: build
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps:
      - uses: appleboy/ssh-action@v0.1.10
        with:
          host: ${{ secrets.SSH_HOST }}
          username: ${{ secrets.SSH_USER }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          script: |
            docker pull myorg/myapp:${{ github.sha }}
            docker tag myorg/myapp:${{ github.sha }} myapp:latest
            cd /opt/myapp && docker-compose up -d

Заключение

Мы получили конвейер, который автоматически проверяет качество кода, собирает образ, сканирует его и деплоит на сервер при пуше в main. Это база, которую можно расширять: добавить stage-окружение, уведомления в Telegram, деплой в Kubernetes.

Помните: автоматизация не заменяет хорошие тесты, но убирает человеческий фактор при развёртывании. А вместе с Docker мы получаем воспроизводимость от локальной машины до продакшена.

← Назад ко всем статьям