Actividad 2. API Spring Boot con GitHub Actions y AWS Elastic Beanstalk

6. FASE 4: Despliegue Continuo (CD) con AWS Elastic Beanstalk

Objetivo de la fase

El objetivo de esta fase es automatizar el despliegue de una API Spring Boot en la nube, de forma que cada cambio subido a GitHub provoque automáticamente la actualización de la aplicación en producción.

En esta fase se pasa de Integración Continua (CI) a Despliegue Continuo (CD), utilizando:

  • GitHub Actions

  • AWS Elastic Beanstalk (plataforma Java SE)

Flujo completo de CD

A partir de esta fase, el flujo real del proyecto será:

git commit
   ↓
git push
   ↓
GitHub Actions
   ↓
Compilación y empaquetado
   ↓
Creación del artefacto de despliegue
   ↓
AWS Elastic Beanstalk
   ↓
Aplicación actualizada automáticamente

No hay pasos manuales intermedios.

BLOQUE A — Laboratorio (AWS Academy / Learner Lab)

En el laboratorio, el objetivo es desplegar manualmente una versión en Elastic Beanstalk y comprobar que la API funciona.
La automatización completa con IAM + Secrets se verá después (Bloque B).
1 Creación de una API mínima Spring Boot

Se parte de una API REST sencilla, cuyo objetivo no es la funcionalidad, sino validar el proceso de despliegue automático.

Endpoint disponible
  • /api/estado -> Devuelve información básica del estado de la aplicación en formato JSON.

Código principal de la aplicación

src/main/java/com/dam/cicd/Man.java

package com.dam.cicd;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Main {
    public static void main(String[] args) {
        SpringApplication.run(Main.class, args);
    }
}

src/main/java/com/dam/cicd/EstadoControlador.java

package com.dam.cicd;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;

@RestController
public class EstadoControlador {

    @GetMapping("/api/estado")
    public Map<String, Object> estado() {
        return Map.of(
                "estado", "OK",
                "servicio", "dam-ci-cd-api-001",
                "mensaje", "API operativa"
        );
    }
}

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <!-- Spring Boot -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.3.5</version>
        <relativePath/>
    </parent>

    <groupId>com.dam.cicd</groupId>
    <artifactId>dam-ci-cd-api-001</artifactId>
    <version>1.0.0</version>
    <name>dam-ci-cd-api-001</name>
    <description>API Spring Boot para CI/CD</description>

    <properties>
        <java.version>17</java.version>
    </properties>

    <dependencies>
        <!-- API REST -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- Tests -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <!-- Genera JAR ejecutable (bootJar) -->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

Comprobamos que:

java -jar target/*.jar

Paramos el Spring Boot y vamos con el código del main

src/main/resources/application.properties

server.port=${PORT:8080}

Esta configuración permite que la aplicación:

  • use el puerto 8080 en local,

  • respete el puerto dinámico asignado por Elastic Beanstalk en la nube.

2 Empaquetado como JAR ejecutable

Elastic Beanstalk (Java SE) despliega JARs ejecutables, no proyectos completos.

Compilación del proyecto

Desde la raíz del proyecto:

./mvnw clean test
./mvnw clean package

Se genera un JAR ejecutable en:

target/dam-ci-cd-api-001-1.0.0.jar
Prueba en local
java -jar target/*.jar

Acceso desde navegador:

  • http://localhost:8080/api/estado

Deberías ver el JSON con estado, servicio, mensaje.

Con esto se valida que:

  • la aplicación arranca correctamente,

  • el JAR es ejecutable,

  • el endpoint responde como se espera.

3 Preparación del paquete de despliegue para Elastic Beanstalk

Elastic Beanstalk espera un ZIP limpio, con el JAR en la raíz y un comando de arranque claro.

Creación del Procfile

En la raíz del proyecto se crea un archivo llamado Procfile (sin extensión):

web: java -jar app.jar

Este archivo indica a Elastic Beanstalk cómo arrancar la aplicación.

Creación del ZIP de despliegue
mkdir -p deploy
cp target/*.jar deploy/app.jar
cp Procfile deploy/Procfile
(cd deploy && zip -r ../deploy.zip .)
 

Resultado final:

deploy.zip
 ├── app.jar
 └── Procfile

Este ZIP es el único artefacto que se despliega en AWS.

 
4 Creación del entorno en Elastic Beanstalk

Desde la consola de AWS:

  1. Abrir Elastic Beanstalk

  2. Pulsar Create application

  3. Configurar:

  • Application name: dam-ci-cd-api-001

  • Platform: Java

  • Platform branch: Java SE

  • Environment tier: Entorno de servidor web

  • Application code: Cargar el código

  • Archivo: deploy.zip

Comprobación final en AWS:

Comprobación final en AWS:

  • Abrir la URL del entorno y verificar:

    • /api/estado responde

    • (opcional) / si lo has añadido como texto “Fin de práctica”.

Hemos creado dos roles

El Ngnix de EB intenta conectar por defecto en el 5000, hay que abrirlo

/api/estado

Fin de la práctica para alumnos de laboratorio

A partir de aquí se continúa con cuenta Free Tier, porque el siguiente bloque requiere gestionar IAM + Secrets, algo que en AWS Academy/Learner Lab puede estar limitado.

BLOQUE B — Continuación (Cuenta Free Tier)
Automatizar el CD con GitHub Actions → Elastic Beanstalk

Objetivo: que cada git push despliegue automáticamente en EB.

5 Bucket S3 para versiones (Elastic Beanstalk)

Elastic Beanstalk usa un bucket automático del tipo:

  • elasticbeanstalk-us-east-1-XXXXXXXXXXXX

No es necesario crear un bucket nuevo si vas a desplegar con EB directamente.

6 Credenciales AWS para GitHub Actions (IAM)
  • IAM → Users → Create user github-actions-eb

  • Policies:

    • AWSElasticBeanstalkFullAccess

    • AmazonS3FullAccess

  • Create access key → te da:

    • AWS_ACCESS_KEY_ID

    • AWS_SECRET_ACCESS_KEY

7 Configurar Secrets en GitHub
  • App Spring Boot empaquetada en JAR

  • Elastic Beanstalk Java SE

  • Secrets en GitHub:

    • AWS_ACCESS_KEY_ID

    • AWS_SECRET_ACCESS_KEY

  • Región: us-east-1

  • App/Entorno: los vas a hardcodear (ok)

Secrets necesarios en GitHub

En tu repo → Settings → Secrets and variables → Actions → New repository secret

Crea estos 4:

  • AWS_ACCESS_KEY_ID

  • AWS_SECRET_ACCESS_KEY

  • AWS_SESSION_TOKEN  

  • AWS_REGION → pon us-east-1

8 Workflow de CD: .github/workflows/deploy-eb.yml

En tu repo crea este archivo: .github/workflows/deploy-eb.yml

Este workflow hace:

  • build del JAR,

  • crea deploy.zip,

  • despliega en Elastic Beanstalk.

Nota importante: en tu YAML final usa tu app real:

  • application_name: dam-ci-cd-api-001

  • environment_name: dam-ci-cd-api-001-env

name: Despliegue a Elastic Beanstalk

on:
  push:
    branches:
      - main   # Se ejecuta cuando haces push a main

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      # Descargar el código del repositorio
      - name: Checkout del repositorio
        uses: actions/checkout@v4

      # Configurar Java 17 (Corretto)
      - name: Configurar Java 17
        uses: actions/setup-java@v4
        with:
          distribution: 'corretto'
          java-version: '17'

      # Compilar el proyecto (ajusta si usas Gradle)
      - name: Compilar proyecto con Maven
        run: mvn clean package -DskipTests

      # Configurar credenciales de AWS
      - name: Configurar credenciales de AWS
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: us-east-1

      # Crear el paquete para Elastic Beanstalk
      - name: Preparar artefacto para Elastic Beanstalk
        run: |
          mkdir deploy
          cp target/*.jar deploy/app.jar
          cp Procfile deploy/Procfile
          cd deploy
          zip -r deploy.zip .

      # Subir nueva versión y desplegar en Elastic Beanstalk
      - name: Desplegar en Elastic Beanstalk
        uses: einaregilsson/beanstalk-deploy@v22
        with:
          aws_access_key: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws_secret_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          application_name: dam-ci-cd-api-001
          environment_name: Dam-ci-cd-api-001-env
          region: us-east-1
          version_label: v-${{ github.run_id }}
          deployment_package: deploy/deploy.zip
          wait_for_environment_recovery: true

9 Reglas de repositorio (evitar subir artefactos)

Comprobamos el estado del git

git status

NO subas deploy.zip  ni deploy/

.gitignore

# Maven
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/

# IntelliJ IDEA
.idea/
*.iml
*.iws
*.ipr

# macOS
.DS_Store


# Artefactos locales de despliegue
/deploy/
/deploy.zip

git status

damiansualdea@Mac-Studio-de-Damian-2 dam-ci-cd-api-001 % git status
On branch main
Your branch is up to date with 'origin/main'.

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        new file:   .github/workflows/deploy-eb.yml
        new file:   Procfile
        new file:   src/main/java/com/dam/cicd/EstadoControlador.java
        new file:   src/main/resources/application.properties

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   .github/workflows/deploy-eb.yml
        modified:   .gitignore
        modified:   Procfile
        modified:   pom.xml
        modified:   src/main/java/com/dam/cicd/EstadoControlador.java
        modified:   src/main/java/com/dam/cicd/Main.java
        modified:   src/main/resources/application.properties

git status

damiansualdea@Mac-Studio-de-Damian-2 dam-ci-cd-api-001 %  git status
On branch main
Your branch is up to date with 'origin/main'.

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        new file:   .github/workflows/deploy-eb.yml
        modified:   .gitignore
        new file:   Procfile
        modified:   pom.xml
        new file:   src/main/java/com/dam/cicd/EstadoControlador.java
        modified:   src/main/java/com/dam/cicd/Main.java
        new file:   src/main/resources/application.properties

10 Resultado esperado (CD real)

Ejecutamos

git commit -m "Configurar API y despliegue automático en Elastic Beanstalk"

Push a GitHub (esto dispara TODO)
 
git push origin main

Ver el pipeline en acción

En cuanto hagas el push, ve a GitHub:

Y en AWS: