Автодеплой
Эта методичка поможет выстроить современный CI/CD процесс деплоя Node.js/React-приложения через GitHub Actions и Docker. Пайплайн будет расти по мере добавления новых задач — от тестов до деплоя.
🔹 Этап 1. Проверка кода: линт и тесты
Создаём отдельный workflow checks.yaml, который принимает context (frontend или backend) и выполняет npm-скрипты.
name: Lint & Tests
on:
workflow_call:
inputs:
context:
required: true
type: string
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Установка зависимостей
working-directory: ${{ inputs.context }}
run: npm ci
- name: Линтинг
working-directory: ${{ inputs.context }}
run: npm run lint
- name: Тестирование
working-directory: ${{ inputs.context }}
run: npm run test🔹 Этап 2. Определение, что изменилось
Создаём джобу detect-changes, которая определяет, были ли изменения во frontend/** или backend/**, и передаёт это дальше.
name: Deploy
on:
push:
branches: [ main ]
jobs:
detect-changes:
runs-on: ubuntu-latest
outputs:
changed_context: ${{ steps.set-context.outputs.context }}
steps:
- uses: actions/checkout@v3
- name: Detect changed services
id: filter
uses: dorny/paths-filter@v3
with:
filters: |
frontend:
- 'frontend/**'
backend:
- 'backend/**'
- name: Set changed context
id: set-context
run: |
if [[ "${{ steps.filter.outputs.frontend }}" == "true" ]]; then
echo "context=frontend" >> $GITHUB_OUTPUT
elif [[ "${{ steps.filter.outputs.backend }}" == "true" ]]; then
echo "context=backend" >> $GITHUB_OUTPUT
else
echo "context=none" >> $GITHUB_OUTPUT
fi🔹 Этап 3. Запуск проверок
Вызываем checks.yaml, если frontend или backend был изменён:
checks:
needs: detect-changes
if: needs.detect-changes.outputs.changed_context != 'none'
uses: ./.github/workflows/checks.yaml
with:
context: ${{ needs.detect-changes.outputs.changed_context }}🔹 Этап 4. Сборка Docker-образа
Билдим и пушим Docker-образ только для изменившегося сервиса:
build:
needs:
- detect-changes
- checks
if: needs.detect-changes.outputs.changed_context != 'none'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USER }}
password: ${{ secrets.DOCKER_PASS }}
- name: Build and push
run: |
docker build -t ${{ secrets.DOCKER_USER }}/devops-${{ needs.detect-changes.outputs.changed_context }}:${{ github.sha }} ./${{ needs.detect-changes.outputs.changed_context }}
docker push ${{ secrets.DOCKER_USER }}/devops-${{ needs.detect-changes.outputs.changed_context }}:${{ github.sha }}🔹 Этап 5. Деплой на VPS
Деплой выполняется только при наличии изменений. Мы подставляем нужный тег образа в .env и перезапускаем docker compose.
deploy:
runs-on: ubuntu-latest
needs:
- detect-changes
- build
if: needs.detect-changes.outputs.changed_context != 'none'
steps:
- name: SSH deploy
uses: appleboy/ssh-action@v0.1.6
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_KEY }}
script: |
IMAGE=${{ secrets.DOCKER_USER }}/devops-${{ needs.detect-changes.outputs.changed_context }}:${{ github.sha }}
docker pull $IMAGE
cd /var/www/devops-intensive.it-incubator.io
if [ "${{ needs.detect-changes.outputs.changed_context }}" = "frontend" ]; then
sed -i "s|^FRONTEND_IMAGE=.*|FRONTEND_IMAGE=$IMAGE|" .env
elif [ "${{ needs.detect-changes.outputs.changed_context }}" = "backend" ]; then
sed -i "s|^BACKEND_IMAGE=.*|BACKEND_IMAGE=$IMAGE|" .env
fi
docker compose up -dДобавляем в секцию script
echo ${{ secrets.DOCKER_PASS }} | docker login -u ${{ secrets.DOCKER_USER }} --password-stdinДобавляем в секцию build
docker build \
-t ${{ secrets.DOCKER_USER }}/devops-${{ needs.detect-changes.outputs.changed_context }}:${{ github.sha }} \
--build-arg VITE_BACKEND_URL=https://devops-intensive.it-incubator.io/api \
./${{ needs.detect-changes.outputs.changed_context }}Добавим изменим команду запуска для Dockerfile на backend
CMD ["sh", "-c", "node scripts/setup-database.js && npm start"]✅ Финальный файл .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
jobs:
detect-changes:
runs-on: ubuntu-latest
outputs:
changed_context: ${{ steps.set-context.outputs.context }}
steps:
- uses: actions/checkout@v3
- name: Detect changed services
id: filter
uses: dorny/paths-filter@v3
with:
filters: |
frontend:
- 'frontend/**'
backend:
- 'backend/**'
- name: Set changed context
id: set-context
run: |
if [[ "${{ steps.filter.outputs.frontend }}" == "true" ]]; then
echo "context=frontend" >> $GITHUB_OUTPUT
elif [[ "${{ steps.filter.outputs.backend }}" == "true" ]]; then
echo "context=backend" >> $GITHUB_OUTPUT
else
echo "context=none" >> $GITHUB_OUTPUT
fi
checks:
needs: detect-changes
if: needs.detect-changes.outputs.changed_context != 'none'
uses: ./.github/workflows/checks.yaml
with:
context: ${{ needs.detect-changes.outputs.changed_context }}
build:
needs:
- detect-changes
- checks
if: needs.detect-changes.outputs.changed_context != 'none'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USER }}
password: ${{ secrets.DOCKER_PASS }}
- name: Build and push
run: |
docker build \
-t ${{ secrets.DOCKER_USER }}/devops-${{ needs.detect-changes.outputs.changed_context }}:${{ github.sha }} \
--build-arg VITE_BACKEND_URL=https://devops-intensive.it-incubator.io/api \
./${{ needs.detect-changes.outputs.changed_context }}
docker push ${{ secrets.DOCKER_USER }}/devops-${{ needs.detect-changes.outputs.changed_context }}:${{ github.sha }}
deploy:
runs-on: ubuntu-latest
needs:
- detect-changes
- build
if: needs.detect-changes.outputs.changed_context != 'none'
steps:
- name: SSH deploy
uses: appleboy/ssh-action@v0.1.6
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_KEY }}
script: |
echo ${{ secrets.DOCKER_PASS }} | docker login -u ${{ secrets.DOCKER_USER }} --password-stdin
IMAGE=${{ secrets.DOCKER_USER }}/devops-${{ needs.detect-changes.outputs.changed_context }}:${{ github.sha }}
docker pull $IMAGE
cd /var/www/devops-intensive.it-incubator.io
if [ "${{ needs.detect-changes.outputs.changed_context }}" = "frontend" ]; then
sed -i "s|^FRONTEND_IMAGE=.*|FRONTEND_IMAGE=$IMAGE|" .env
elif [ "${{ needs.detect-changes.outputs.changed_context }}" = "backend" ]; then
sed -i "s|^BACKEND_IMAGE=.*|BACKEND_IMAGE=$IMAGE|" .env
fi
docker compose up -dТеперь у тебя минималистичный и масштабируемый пайплайн: всё собирается и деплоится только при реальных изменениях.