GitHub Actions: Construir y Desplegar Imagenes Docker Automaticamente

Construir imagenes Docker manualmente y subirlas a un registro cada vez que cambias el codigo es un proceso repetitivo y propenso a errores. GitHub Actions permite automatizar completamente este flujo — desde el build de la imagen hasta su publicacion en GHCR o Docker Hub — cada vez que haces push a una rama o creas un tag de release. Esta guia cubre la creacion de workflows de CI/CD robustos para proyectos basados en Docker.

Workflow Basico de Build y Push

Un workflow minimo necesita autenticarse con el registro de contenedores, construir la imagen y publicarla. GitHub Container Registry (GHCR) es la opcion mas sencilla ya que utiliza el GITHUB_TOKEN automatico sin necesidad de configurar secretos adicionales.

# .github/workflows/docker-build.yml
name: Build and Push Docker Image

on:
  push:
    branches: [main]
    tags: ['v*']
  pull_request:
    branches: [main]

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

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Log in to Container Registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract metadata (tags, labels)
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=ref,event=branch
            type=semver,pattern={{version}}
            type=sha,prefix=

      - name: Build and push Docker image
        uses: docker/build-push-action@v5
        with:
          context: .
          push: ${{ github.event_name != 'pull_request' }}
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}

Este workflow se activa en pushes a la rama main y en tags que comienzan con v. Para pull requests, construye la imagen sin publicarla (push: false), lo que permite validar que el Dockerfile compila correctamente antes de fusionar los cambios.

La accion docker/metadata-action genera automaticamente las etiquetas de la imagen basandose en el contexto de Git. Para un tag v1.2.3, genera la etiqueta 1.2.3. Para la rama main, genera la etiqueta main. Esto elimina la gestion manual de versiones de imagenes.

Builds Multi-Arquitectura

Las aplicaciones modernas necesitan ejecutarse en diferentes arquitecturas, especialmente con la adopcion creciente de procesadores ARM como los chips Apple Silicon y AWS Graviton. Docker Buildx permite construir imagenes para multiples plataformas en un solo workflow.

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3

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

      - name: Log in to Container Registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build and push multi-platform image
        uses: docker/build-push-action@v5
        with:
          context: .
          platforms: linux/amd64,linux/arm64
          push: true
          tags: ghcr.io/${{ github.repository }}:latest
          cache-from: type=gha
          cache-to: type=gha,mode=max

QEMU proporciona la emulacion necesaria para compilar en arquitecturas diferentes a la del runner. La opcion cache-from y cache-to con type=gha usa el cache de GitHub Actions para acelerar builds posteriores, reduciendo significativamente los tiempos de construccion.

Escaneo de Vulnerabilidades

Integrar un escaneo de seguridad en el pipeline garantiza que las imagenes publicadas no contengan vulnerabilidades conocidas. Trivy es una herramienta popular de codigo abierto que escanea imagenes Docker, sistemas de archivos y configuraciones de IaC.

      - name: Build image for scanning
        uses: docker/build-push-action@v5
        with:
          context: .
          load: true
          tags: ${{ env.IMAGE_NAME }}:scan

      - name: Run Trivy vulnerability scanner
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: ${{ env.IMAGE_NAME }}:scan
          format: 'sarif'
          output: 'trivy-results.sarif'
          severity: 'CRITICAL,HIGH'

      - name: Upload Trivy scan results
        uses: github/codeql-action/upload-sarif@v3
        if: always()
        with:
          sarif_file: 'trivy-results.sarif'

El paso load: true carga la imagen en el daemon Docker local en lugar de publicarla, permitiendo que Trivy la escanee antes del push. Los resultados en formato SARIF se cargan a la pestana Security de GitHub, proporcionando visibilidad sobre vulnerabilidades directamente en la interfaz del repositorio.

Estrategias de Despliegue

Una vez que la imagen esta publicada en el registro, el paso final es desplegarla en el entorno de destino. Las estrategias varian segun la infraestructura: actualizacion directa via SSH para servidores simples, actualizacion del manifiesto de Kubernetes para clusters orquestados, o trigger de un pipeline de despliegue separado.

Para servidores que ejecutan Docker Compose, puedes agregar un step que se conecte por SSH y actualice los contenedores:

      - name: Deploy to production
        if: github.ref == 'refs/heads/main'
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.DEPLOY_HOST }}
          username: ${{ secrets.DEPLOY_USER }}
          key: ${{ secrets.DEPLOY_SSH_KEY }}
          script: |
            cd /opt/app
            docker compose pull
            docker compose up -d --remove-orphans

Para entornos de Kubernetes, considera usar herramientas como ArgoCD o Flux que detectan automaticamente nuevas imagenes en el registro y aplican la actualizacion siguiendo una estrategia de rolling update. Esta aproximacion GitOps desacopla el build del despliegue y proporciona un historial completo de cambios en el cluster.

Articulos Relacionados