Guides
Fundamentos ▾
Versionamento ▾
Deploy ▾

CI/CD Comparado

GitHub Actions, GitLab CI, Jenkins, Drone — do commit ao deploy automatizado.

Mapa de Decisão

Código já no GitHub? → GitHub Actions (nativo)
Código no GitLab?    → GitLab CI (nativo)
Controle total?      → Jenkins (self-hosted)
Leve e container?    → Drone (self-hosted)
Multi-plataforma?    → GitHub Actions (mais amplo)
GitHub Actions
SaaS, grátis para públicos, 20k+ actions
GitLab CI
SaaS/self-hosted, 400 min grátis
Jenkins
Self-hosted, 1800+ plugins, Groovy
Drone
Self-hosted, YAML mínimo, Docker first

Comparação Geral

AspectoGitHub ActionsGitLab CIJenkinsDrone
HospedagemSaaSSaaS/self-hostedSelf-hostedSelf-hosted
Gratuito (público)✅ ilimitado400 min/mêsN/A✅ open source
Docker nativo✅ via actions✅ servicesvia plugin✅ first-class
Marketplace20k+ actionstemplates1800+ plugins100+ plugins
Curva aprendizadomédiamédiaaltabaixa
Complexidade YAMLmédiamédiaGroovymínima

GitHub Actions — Básico

name: CI

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

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: npm ci
      - run: npm run build
      - run: npm test

Eventos

on:
  push:
    branches: [main]
    paths: ['src/**']
  pull_request:
    types: [opened, synchronize, reopened]
  schedule:
    - cron: '0 6 * * 1'
  workflow_dispatch:
    inputs:
      environment:
        type: choice
        options: [staging, production]
  release:
    types: [published]

Matrix Builds

jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        node: [18, 20, 22]
        os: [ubuntu-latest, windows-latest]
      fail-fast: false
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node }}
      - run: npm ci && npm test

Services (Containers)

jobs:
  test:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:16
        env:
          POSTGRES_USER: test
          POSTGRES_PASSWORD: test
          POSTGRES_DB: test_db
        ports: ['5432:5432']
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
      redis:
        image: redis:7
        ports: ['6379:6379']

Secrets e Variáveis

# secrets (criptografados)
${{ secrets.SSH_KEY }}
${{ secrets.DOCKER_PASSWORD }}

# variáveis (não sensíveis)
${{ vars.DEPLOY_PATH }}

# variáveis automáticas
${{ github.token }}
${{ github.repository }}
${{ github.ref }}

# CLI
gh secret set SSH_KEY < ~/.ssh/id_ed25519
gh secret list

Reusable Workflows

# .github/workflows/reusable-deploy.yml
on:
  workflow_call:
    inputs:
      environment:
        required: true
        type: string
    secrets:
      SSH_KEY:
        required: true

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: ${{ inputs.environment }}
    steps:
      - uses: actions/checkout@v4

# chamando
jobs:
  deploy-staging:
    uses: ./.github/workflows/reusable-deploy.yml
    with:
      environment: staging
    secrets:
      SSH_KEY: ${{ secrets.SSH_KEY_STAGING }}

Exemplo Completo — CI/CD Node.js

name: CI/CD

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

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 20, cache: npm }
      - run: npm ci
      - run: npm run lint

  test:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:16
        env:
          POSTGRES_USER: test
          POSTGRES_PASSWORD: test
          POSTGRES_DB: test
        ports: ['5432:5432']
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 20, cache: npm }
      - run: npm ci
      - run: npm test
        env:
          DATABASE_URL: postgres://test:test@localhost:5432/test

  build:
    needs: [lint, test]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 20, cache: npm }
      - run: npm ci && npm run build
      - uses: actions/upload-artifact@v4
        with:
          name: dist
          path: dist/

  deploy:
    needs: build
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    environment: production
    steps:
      - uses: actions/checkout@v4
      - uses: actions/download-artifact@v4
        with: { name: dist, path: dist/ }
      - uses: appleboy/ssh-action@v1.0.3
        with:
          host: ${{ secrets.SSH_HOST }}
          username: ${{ secrets.SSH_USER }}
          key: ${{ secrets.SSH_KEY }}
          script: |
            cd ~/apps/meu-app
            git pull origin main
            npm ci --production
            pm2 reload meu-app

GitLab CI

stages:
  - build
  - test
  - deploy

variables:
  NODE_VERSION: "20"

build:
  stage: build
  image: node:${NODE_VERSION}
  script:
    - npm ci
    - npm run build
  artifacts:
    paths: [dist/]
    expire_in: 1 hour

test:
  stage: test
  image: node:${NODE_VERSION}
  services:
    - postgres:16
  variables:
    POSTGRES_USER: test
    POSTGRES_PASSWORD: test
    DATABASE_URL: postgres://test:test@postgres:5432/test
  script:
    - npm ci && npm test

deploy_production:
  stage: deploy
  script:
    - ./deploy.sh production
  environment:
    name: production
    url: https://meu-app.com
  when: manual
  only:
    - main

Jenkins

pipeline {
    agent any

    environment {
        NODE_VERSION = '20'
    }

    stages {
        stage('Install') {
            steps { sh 'npm ci' }
        }
        stage('Lint') {
            steps { sh 'npm run lint' }
        }
        stage('Test') {
            steps { sh 'npm test' }
        }
        stage('Build') {
            steps {
                sh 'npm run build'
                archiveArtifacts artifacts: 'dist/**'
            }
        }
        stage('Deploy') {
            when { branch 'main' }
            input { message 'Deploy produção?' }
            steps {
                sshagent(['ssh-prod']) {
                    sh 'scp -r dist/* user@prod:/var/www/app/'
                }
            }
        }
    }

    post {
        failure {
            mail to: 'team@example.com',
                 subject: "FAILED: ${currentBuild.fullDisplayName}"
        }
    }
}

Drone CI

kind: pipeline
type: docker
name: default

steps:
  - name: test
    image: node:20
    commands:
      - npm ci
      - npm test

  - name: build
    image: node:20
    commands:
      - npm run build

  - name: deploy
    image: appleboy/drone-ssh
    settings:
      host:
        from_secret: ssh_host
      username: root
      key:
        from_secret: ssh_key
      script:
        - cd ~/apps/meu-app
        - git pull origin main
        - pm2 reload meu-app
    when:
      branch: main
      event: push

Referência Rápida

SituaçãoGitHub ActionsGitLab CIJenkins
Trigger pushon: push:rules: if:when { branch }
Serviço DBservices: postgres:services: - postgres:16Docker plugin
Cacheactions/cache@v4cache: key:Workspace
Deploy manualworkflow_dispatchwhen: manualinput step
Secrets${{ secrets.X }}Variables maskéesCredentials
Matrixstrategy: matrix:parallel: matrix:parallel { }
Artifactupload-artifactartifacts: paths:archiveArtifacts
Self-hostedruns-on: self-hostedtags: [docker]agent { label }