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
| Aspecto | GitHub Actions | GitLab CI | Jenkins | Drone |
|---|---|---|---|---|
| Hospedagem | SaaS | SaaS/self-hosted | Self-hosted | Self-hosted |
| Gratuito (público) | ✅ ilimitado | 400 min/mês | N/A | ✅ open source |
| Docker nativo | ✅ via actions | ✅ services | via plugin | ✅ first-class |
| Marketplace | 20k+ actions | templates | 1800+ plugins | 100+ plugins |
| Curva aprendizado | média | média | alta | baixa |
| Complexidade YAML | média | média | Groovy | mí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ção | GitHub Actions | GitLab CI | Jenkins |
|---|---|---|---|
| Trigger push | on: push: | rules: if: | when { branch } |
| Serviço DB | services: postgres: | services: - postgres:16 | Docker plugin |
| Cache | actions/cache@v4 | cache: key: | Workspace |
| Deploy manual | workflow_dispatch | when: manual | input step |
| Secrets | ${{ secrets.X }} | Variables maskées | Credentials |
| Matrix | strategy: matrix: | parallel: matrix: | parallel { } |
| Artifact | upload-artifact | artifacts: paths: | archiveArtifacts |
| Self-hosted | runs-on: self-hosted | tags: [docker] | agent { label } |