{}const=>[]async()letfn</>var
DesarrolloFundamentos

CI/CD en pocas palabras: despliegue automático en una noche

Una explicación sencilla de CI/CD y un ejemplo listo para usar de autodeploy a través de GitHub Actions.

К

Kodik

Autor

4 min de lectura

Qué es CI/CD y por qué lo necesitas

  • CI (Continuous Integration) — recopilamos y probamos cada edición automáticamente.

  • CD (Continuous Delivery/Deployment) — después del éxito, lo llevamos al stand/producto sin manos.

Ventajas: menos errores manuales, lanzamientos rápidos, transparencia para el equipo y despliegues predecibles. 🧘‍♂️

🔥 100.000+ estudiantes ya están con nosotros

¿Cansado de leer teoría?
¡Hora de programar!

Kodik — una app donde aprendes a programar con práctica. Mentor IA, lecciones interactivas, proyectos reales.

🤖 IA 24/7
🎓 Certificados
💰 Gratis
🚀 Empezar
Se unieron hoy

Servidor: formación básica (VPS)

# 1) Docker y el plugin compose (Ubuntu)
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER

# 2) Catálogo de aplicaciones
sudo mkdir -p /opt/myapp && sudo chown -R $USER:$USER /opt/myapp
cd /opt/myapp

# 3) .env (en el servidor, no se confirma)
cat > .env << 'ENV'
PORT=3000
ENV

# 4) docker-compose.yml
cat > docker-compose.yml << 'YAML'
services:
  web:
    image: registry.example.com/myapp:latest
    env_file: .env
    ports:
      - "80:3000"
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 10s
      timeout: 3s
      retries: 5
YAML

# 5) Primer lanzamiento (sin CI por ahora)
docker compose pull && docker compose up -d

Dockerfile (ejemplo mínimo para Node/PNPM)

# Dockerfile
FROM node:20-alpine AS deps
RUN corepack enable && corepack prepare pnpm@latest --activate
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN pnpm i --frozen-lockfile

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

FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=build /app/dist dist
COPY package.json ./
RUN corepack enable && corepack prepare pnpm@latest --activate && pnpm i --prod --frozen-lockfile
EXPOSE 3000
CMD ["node", "dist/server.js"]

GitHub Actions: compilación de imágenes e implementación de SSH

Añade .github/workflows/deploy.yml al repositorio:

name: CI/CD Deploy

on:
  push:
    branches: [ "main" ]

permissions:
  contents: read
  packages: write

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

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

      - name: Login to registry
        uses: docker/login-action@v3
        with:
          registry: ${{ secrets.REGISTRY_URL }}
          username: ${{ secrets.REGISTRY_USER }}
          password: ${{ secrets.REGISTRY_PASSWORD }}

      - name: Build & push image
        uses: docker/build-push-action@v6
        with:
          push: true
          context: .
          tags: ${{ secrets.REGISTRY_URL }}/${{ secrets.IMAGE_NAME }}:latest

      - name: Deploy via SSH
        uses: appleboy/ssh-action@v1.0.0
        with:
          host:     ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key:      ${{ secrets.SERVER_SSH_KEY }}
          script: |
            set -e
            cd /opt/myapp
            docker compose pull
            docker compose up -d
            docker image prune -f

Secretos: REGISTRY_URL, REGISTRY_USER, REGISTRY_PASSWORD, IMAGE_NAME, SERVER_HOST, SERVER_USER, SERVER_SSH_KEY.

GitLab CI: una alternativa

# .gitlab-ci.yml
stages: [build, deploy]

variables:
  IMAGE: $CI_REGISTRY_IMAGE:latest

build:
  stage: build
  image: docker:27.0
  services: [ "docker:27.0-dind" ]
  script:
    - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
    - docker build -t $IMAGE .
    - docker push $IMAGE
  artifacts:
    expire_in: 1 week
    when: on_success
    paths: []

deploy:
  stage: deploy
  image: alpine:latest
  before_script:
    - apk add --no-cache openssh-client
    - eval $(ssh-agent -s)
    - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
  script:
    - ssh -o StrictHostKeyChecking=no $DEPLOY_USER@$DEPLOY_HOST "
        set -e
        cd /opt/myapp &&
        docker compose pull &&
        docker compose up -d &&
        docker image prune -f
      "
  only:
    - main

Cero tiempo de inactividad y retrocesos

  • Zero‑downtime: restart: unless-stopped + healthcheck + up -d — el nuevo contenedor se inicia, el antiguo se apaga.

  • Etiquetas de lanzamiento: además de :latest envíe :v1.4.2 — es más fácil revertir.

  • Rollback: cambie la etiqueta a docker-compose.yml y repita pull/up -d.

  • Esquema azul/verde (simplificado): mantén dos servicios web_blue y web_green, equilibra el tráfico a través de Nginx.

Comprobaciones mínimas en el pipeline

# ejemplo de un paso con pruebas en GitHub Actions
- name: Install deps & test
  run: |
    npm ci
    npm run test -- --ci

Umbral para el CI «nocturno»: ejecutar las pruebas unitarias, compilar, pasar el linter (ESLint/flake8), verificar los tipos (tsc/mypy).

Monitoreo y registro

  • Añada /health y /metrics (formato Prometheus) al servicio.

  • Recopilación de registros: docker logs → Loki/ELK; alertas de código de estado/latencia.

  • En CI, artefactos con informes de pruebas y linter para ver regresiones.

Secretos y seguridad

  • Secretos solo en Secrets/Variables CI; .env — en el servidor.

  • Deshabilite el inicio de sesión SSH con contraseña, deje el inicio de sesión con clave, restrinja los puertos.

  • No almacenes claves privadas en el repositorio, ni siquiera en forma cifrada.

🧯 Problemas típicos y soluciones rápidas

Síntoma

Motivo

Fijo

El CI se cae en el montaje

Poca RAM/tiempo de espera agotado

Caché de capas, base de imagen más ligera, aumentar el tiempo de espera

La aplicación no se inicia

Puerto/ENV/migración

Comprueba .env, docker logs, healthcheck, ejecuta migraciones

Despliegue lento

Imagen pesada

Multi-stage, alpine, .dockerignore, almacenamiento en caché de dependencias

«Funciona para mí»

Diferentes versiones de Node/Python

Fijar versiones en Docker, usar archivos de bloqueo

Disco lleno

Imágenes/contenedores antiguos

docker system prune -f, rotación de registros

En el anexo «Kodik: aprendizaje de programación» — lecciones cortas y miniproyectos. ¡Nosotros somos interesantes!

Y también tenemos un canal de telegram, donde discutimos ideas geniales, compartimos experiencias y analizamos juntos las tareas: aprender no solo es útil, sino también divertido.

Total

CI/CD no es «gran magia», sino un conjunto de pasos simples. Compilar una imagen, enviarla al registro, reiniciar el servicio en VPS y tendrás versiones rápidas y repetibles sin una rutina manual. Empieza hoy y mañana el equipo olvidará cómo era el «despliegue manual».

¿En qué vas a ejecutar el despliegue automático: GitHub Actions o GitLab CI?

🎯Deja de postergar

¿Te gustó el artículo?
¡Hora de practicar!

En Kodik no solo lees — escribes código de inmediato. Teoría + práctica = habilidades reales.

Práctica instantánea
🧠IA explica código
🏆Certificado

Sin registro • Sin tarjeta