CI/CD для Node.js проекта

Практическое руководство по настройке CI/CD пайплайна для Node.js/TypeScript приложения с GitHub Actions: тесты, линтинг, сборка Docker-образа и деплой.

Зачем нужно

Node.js-проект без CI/CD требует ручной проверки и деплоя при каждом изменении. Автоматический пайплайн запускает тесты и линтер на каждый PR, предотвращая попадание сломанного кода в main. После слияния тот же пайплайн автоматически деплоит приложение.

Где используется

  • Express/Fastify API: CI тестирует эндпоинты, CD деплоит на VPS или контейнер
  • Next.js/Nuxt: сборка и деплой на Vercel или собственный сервер
  • npm-пакет: автопубликация в npm registry при создании тега
  • Monorepo: раздельные пайплайны для frontend и backend

Основной контент

Полный CI пайплайн

# .github/workflows/ci.yml
name: CI

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

jobs:
  lint-and-test:
    runs-on: ubuntu-latest

    services:
      postgres:                     # если нужна БД для интеграционных тестов
        image: postgres:16
        env:
          POSTGRES_PASSWORD: test
          POSTGRES_DB: testdb
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm

      - run: npm ci

      - name: Lint
        run: npm run lint

      - name: Type check
        run: npx tsc --noEmit

      - name: Test
        run: npm test -- --coverage
        env:
          DATABASE_URL: postgres://postgres:test@localhost:5432/testdb
          NODE_ENV: test

CD пайплайн: сборка Docker + деплой

# .github/workflows/deploy.yml
name: Deploy

on:
  push:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    outputs:
      image-tag: ${{ steps.meta.outputs.tags }}
    steps:
      - uses: actions/checkout@v4

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

      - name: Log in to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_TOKEN }}

      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: myuser/myapp
          tags: type=sha

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

  deploy:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - name: Deploy to VPS
        uses: appleboy/ssh-action@v1.0.0
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: deploy
          key: ${{ secrets.SSH_KEY }}
          script: |
            docker pull myuser/myapp:${{ github.sha }}
            docker stop myapp || true
            docker run -d --rm \
              --name myapp \
              -p 3000:3000 \
              --env-file /etc/myapp.env \
              myuser/myapp:${{ github.sha }}

Автопубликация пакета в npm

name: Publish to npm

on:
  push:
    tags: ['v*']

jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          registry-url: https://registry.npmjs.org
      - run: npm ci
      - run: npm publish
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

Частые ошибки

  • Использование npm install вместо npm ci — нарушает воспроизводимость сборки
  • Переменные окружения не заданы для тестовой среды — тесты падают только в CI
  • Нет needs: зависимости между CI и CD джобами — деплой запускается параллельно с тестами
  • Docker-образ пересобирается с нуля при каждом запуске — нет кэша слоёв (cache-from/cache-to)

Связанные темы

Ресурсы