DevToolBoxGRATIS
Blog

GitHub Actions + Docker CI/CD: Build, Push y Despliegue

13 minpor DevToolBox

Una pipeline CI/CD robusta con GitHub Actions y Docker automatiza todo el ciclo de vida del despliegue. Esta guia construye una pipeline de nivel produccion con builds multiplataforma y despliegues sin tiempo de inactividad.

Descripcion general de la pipeline

La pipeline tiene cuatro etapas: CI (prueba), Build (imagen Docker), Push (al registro) y Deploy (al servidor).

Pipeline Flow:
  git push → GitHub Actions triggered
       │
       ├─ [Job 1] CI: npm test, lint, type-check
       │         (runs on all PRs and main pushes)
       │
       ├─ [Job 2] Build: docker build --platform linux/amd64,linux/arm64
       │         (runs only on main branch push)
       │         Push to ghcr.io/org/app:sha-abc1234
       │
       └─ [Job 3] Deploy: SSH to server, docker pull + restart
                 (runs after successful build)
                 Zero-downtime with blue-green or rolling restart

Mejores practicas para Dockerfile

Un Dockerfile bien estructurado es la base. Las builds multi-etapa reducen significativamente el tamano final de la imagen.

# Multi-stage Dockerfile (Node.js example)

# Stage 1: Dependencies
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production && cp -R node_modules /prod_node_modules
RUN npm ci   # install ALL deps for building

# Stage 2: Builder
FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build

# Stage 3: Production runner (minimal image)
FROM node:20-alpine AS runner
WORKDIR /app

# Security: run as non-root user
RUN addgroup --system --gid 1001 nodejs \
 && adduser --system --uid 1001 appuser

# Copy only what's needed for runtime
COPY --from=deps /prod_node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package.json ./

USER appuser
EXPOSE 3000

# Health check for orchestrators
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s \
  CMD wget -qO- http://localhost:3000/health || exit 1

CMD ["node", "dist/index.js"]

# Result: ~150MB instead of ~800MB for the same app

Pipeline CI/CD basica

Este workflow construye y hace push a GitHub Container Registry en cada push a main.

# .github/workflows/deploy.yml

name: CI/CD Pipeline

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

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  # Stage 1: Test
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm test

      - name: Run lint
        run: npm run lint

  # Stage 2: Build and Push Docker image
  build-push:
    needs: test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    permissions:
      contents: read
      packages: write

    outputs:
      image-digest: ${{ steps.build.outputs.digest }}

    steps:
      - uses: actions/checkout@v4

      - name: Login to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}  # Auto-provided by GitHub

      - name: Extract Docker metadata (tags, labels)
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=ref,event=branch
            type=sha,prefix=sha-
            type=raw,value=latest,enable={{is_default_branch}}

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

  # Stage 3: Deploy to server
  deploy:
    needs: build-push
    runs-on: ubuntu-latest
    environment: production  # GitHub Environment with protection rules

    steps:
      - name: Deploy to production server via SSH
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          script: |
            # Pull latest image
            echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin
            docker pull ghcr.io/${{ github.repository }}:latest

            # Zero-downtime restart
            docker compose -f /app/docker-compose.prod.yml up -d --no-deps app

            # Verify deployment
            sleep 10
            docker compose -f /app/docker-compose.prod.yml ps

Caracteristicas avanzadas de la pipeline

Las pipelines de produccion necesitan caracteristicas adicionales: builds multiplataforma, escaneo de seguridad y rollback.

# Advanced Pipeline Features

# 1. Multi-platform builds (AMD64 + ARM64)
- name: Set up QEMU
  uses: docker/setup-qemu-action@v3

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

- name: Build multi-platform image
  uses: docker/build-push-action@v5
  with:
    platforms: linux/amd64,linux/arm64
    push: true
    tags: ${{ steps.meta.outputs.tags }}

# 2. Security scanning with Trivy
- name: Scan image for vulnerabilities
  uses: aquasecurity/trivy-action@master
  with:
    image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
    format: 'sarif'
    output: 'trivy-results.sarif'
    severity: 'CRITICAL,HIGH'
    exit-code: '1'  # Fail pipeline on critical vulnerabilities

# 3. Semantic versioning with tags
on:
  push:
    tags:
      - 'v*.*.*'

jobs:
  release:
    steps:
      - name: Extract version from tag
        id: version
        run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT

      - uses: docker/build-push-action@v5
        with:
          tags: |
            ghcr.io/myorg/myapp:${{ steps.version.outputs.VERSION }}
            ghcr.io/myorg/myapp:latest

# 4. Rollback on failure
- name: Deploy with rollback
  run: |
    PREVIOUS_IMAGE=$(docker inspect --format='{{.Config.Image}}' app-container)
    docker pull $NEW_IMAGE

    if docker run --rm $NEW_IMAGE node -e "require('./dist/index.js')"; then
      docker stop app-container
      docker run -d --name app-container $NEW_IMAGE
    else
      echo "New image failed healthcheck, keeping previous"
      exit 1
    fi

Preguntas frecuentes

Como almacenar secrets en GitHub Actions?

Ve a los ajustes del repositorio → Secrets and variables → Actions → New repository secret.

Diferencia entre ghcr.io y Docker Hub?

GitHub Container Registry es gratuito para repositorios publicos e integrado con los permisos de GitHub.

Como construir para AMD64 y ARM64?

Usa docker/setup-qemu-action y docker/setup-buildx-action.

Como implementar despliegues especificos por entorno?

Usa los entornos de GitHub para staging y produccion.

Herramientas relacionadas

𝕏 Twitterin LinkedIn
¿Fue útil?

Mantente actualizado

Recibe consejos de desarrollo y nuevas herramientas.

Sin spam. Cancela cuando quieras.

Prueba estas herramientas relacionadas

YMLYAML Validator & Formatter{ }JSON Formatter→BBase64 Decoder

Artículos relacionados

Tutorial Docker Compose: De los fundamentos a stacks listos para produccion

Tutorial completo de Docker Compose: sintaxis docker-compose.yml, servicios, redes, volumenes, variables de entorno, healthchecks y ejemplos con Node.js/Python/WordPress.

Estrategias de ramificacion Git: GitFlow vs Trunk-Based vs GitHub Flow

Compara las estrategias GitFlow, Trunk-Based Development y GitHub Flow. Estructuras de ramas, flujos de merge, integracion CI/CD y como elegir la estrategia correcta.

Kubernetes para principiantes: Tutorial completo (2026)

Aprende Kubernetes desde cero. Pods, Services, Deployments y más.