Автоматизируем деплой: CI/CD пайплайн на GitHub Actions с Docker
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 мы получаем воспроизводимость от локальной машины до продакшена.