Как был настроен процесс деплоя в вашем проекте на Node.js?

Ответ

В последнем production-проекте мы использовали CI/CD пайплайн на GitHub Actions с Docker. Целью была полная автоматизация от пуша кода в main до обновления сервиса на сервере.

Основные этапы пайплайна (.github/workflows/deploy.yml):

  1. Тестирование и сборка:

    jobs:
      test-and-build:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
          - name: Setup Node.js
            uses: actions/setup-node@v4
            with: { node-version: '20' }
          - run: npm ci
          - run: npm run lint          # ESLint
          - run: npm test             # Jest
          - run: npm run build        # Если есть сборка (TypeScript, etc.)
  2. Сборка и публикация Docker-образа:

      build-and-push:
        needs: test-and-build
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
          - name: Build Docker image
            run: docker build -t ${{ secrets.DOCKER_USERNAME }}/my-app:${{ github.sha }} .
          - name: Push to Docker Hub
            run: |
              echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin
              docker push ${{ secrets.DOCKER_USERNAME }}/my-app:${{ github.sha }}
  3. Деплой на продакшен-сервер:

      deploy:
        needs: build-and-push
        runs-on: ubuntu-latest
        steps:
          - name: Deploy via SSH
            uses: appleboy/ssh-action@v1
            with:
              host: ${{ secrets.SSH_HOST }}
              username: ${{ secrets.SSH_USER }}
              key: ${{ secrets.SSH_PRIVATE_KEY }}
              script: |
                cd /opt/my-app
                docker-compose pull
                docker-compose up -d --remove-orphans
                docker image prune -f

Инфраструктура на сервере:

  • Приложение и зависимости (PostgreSQL, Redis) описаны в docker-compose.prod.yml.
  • Nginx как reverse proxy и для раздачи статики.
  • PM2 или аналогичный процесс-менеджер не требуется, так как контейнером управляет Docker.
  • Для staging-окружения использовался аналогичный пайплайн, но с триггером на пуши в ветку develop и деплоем на отдельный сервер.

Ответ 18+ 🔞

Слушай, а вот это реально годная штука, когда всё само катается от пуша до прода. Мы на прошлом проекте так и сделали — настроили CI/CD на GitHub Actions, чтобы не ебаться руками каждый раз. Цель была простая: закоммитил в main — и через пятнадцать минут твой сервис уже обновился на боевом сервере, пока ты кофе пьёшь. Ёперный театр, удобно же!

Вот как этот пайплайн в .github/workflows/deploy.yml выглядел, по шагам:

  1. Сначала всё проверяем и собираем:

    jobs:
      test-and-build:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
          - name: Setup Node.js
            uses: actions/setup-node@v4
            with: { node-version: '20' }
          - run: npm ci
          - run: npm run lint          # ESLint
          - run: npm test             # Jest
          - run: npm run build        # Если есть сборка (TypeScript, etc.)

    То есть, сначала линтер прогоняем, потом тесты. Если тесты не прошли — всё, приехали, дальше пайплайн накрывается медным тазом. И правильно, зачем нам кривой код деплоить?

  2. Потом Docker-образ лепим и в репозиторий пихаем:

      build-and-push:
        needs: test-and-build
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
          - name: Build Docker image
            run: docker build -t ${{ secrets.DOCKER_USERNAME }}/my-app:${{ github.sha }} .
          - name: Push to Docker Hub
            run: |
              echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin
              docker push ${{ secrets.DOCKER_USERNAME }}/my-app:${{ github.sha }}

    Тут прикол в теге ${{ github.sha }} — каждый коммит получает свой уникальный образ. Хуй с горы, зато всегда можно откатиться к конкретной версии.

  3. Ну и финальный аккорд — деплой на сервак:

      deploy:
        needs: build-and-push
        runs-on: ubuntu-latest
        steps:
          - name: Deploy via SSH
            uses: appleboy/ssh-action@v1
            with:
              host: ${{ secrets.SSH_HOST }}
              username: ${{ secrets.SSH_USER }}
              key: ${{ secrets.SSH_PRIVATE_KEY }}
              script: |
                cd /opt/my-app
                docker-compose pull
                docker-compose up -d --remove-orphans
                docker image prune -f

    Подключается по SSH, заходит в папку, дёргает свежий образ и перезапускает контейнеры. Команда prune -f старые образы подчищает, чтобы диск не засрать. Красота, ебушки-воробушки!

А на сервере что творилось:

  • Всё хозяйство — приложение, PostgreSQL, Redis — в одном docker-compose.prod.yml. Порядок, бля.
  • Nginx спереди как reverse proxy, он же статику отдаёт.
  • PM2 или какой другой процесс-менеджер был нахуй не нужен, потому что контейнер сам как сервис работает. Доверия ебать ноль к этим менеджерам, когда есть Docker.
  • Для тестового стенда (staging) был такой же пайплайн, но он срабатывал на пуши в ветку develop и катал всё на отдельный сервер. Чтобы не бздеть, что сломаем продакшен.