API Laravel — Despliegue en AWS EC2 con CI/CD

Esta memoria técnica documenta el proceso completo para levantar una API REST en Laravel en un servidor AWS EC2, exponiéndola a través de Apache 2 con PHP 8.2 y MariaDB como base de datos. Además, se implementó un pipeline de CI/CD con GitHub Actions que automatiza la instalación de dependencias, la compilación de assets con Vite y el despliegue hacia el servidor.

Ver API en el servidor EC2

Objetivo del proyecto

El objetivo principal fue diseñar e implementar una API Laravel genérica lista para producción, desplegada en AWS EC2, con:

Arquitectura general

Cliente (browser / API client)
      ↓ HTTP / HTTPS
Apache 2 (VirtualHost)
      ↓ DocumentRoot: /var/www/laravel-api-frutas/public
PHP 8.2
      ↓
Aplicación Laravel 12 (API REST)
      ↓
MariaDB (instancia local en EC2)

Tecnologías y herramientas

Preparación del servidor EC2 y VirtualHost de Apache

En la instancia EC2 se realizaron los siguientes pasos para preparar el entorno de Laravel:

Posteriormente se definió un VirtualHost dedicado para la API:

<VirtualHost *:80>
    ServerName 78.13.228.8

    DocumentRoot /var/www/laravel-api-frutas/public

    <Directory /var/www/laravel-api-frutas/public>
        AllowOverride All
        Require all granted
    </Directory>

    ErrorLog ${APACHE_LOG_DIR}/laravel-api-error.log
    CustomLog ${APACHE_LOG_DIR}/laravel-api-access.log combined
</VirtualHost>

Configuración de la base de datos

La base de datos se configuró directamente desde la consola de MariaDB, sin herramientas externas:

Pipeline de CI/CD con GitHub Actions

El despliegue se automatizó con un workflow llamado Deploy Laravel API to EC2 ubicado en .github/workflows/deploy.yml:

name: Deploy Laravel API to EC2

on:
  push:
    branches:
      - main
  workflow_dispatch:

jobs:
  deploy:
    runs-on: ubuntu-latest

    env:
      APP_DIR: /var/www/laravel-api-frutas

    steps:
      # 1) Descargar código del repo
      - name: Checkout code
        uses: actions/checkout@v4

      # 2) Configurar PHP 8.2 + Composer
      - name: Set up PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.2'
          extensions: mbstring, bcmath, intl, pdo_mysql, curl, xml, ctype, json, tokenizer
          coverage: none
          tools: composer

      # 3) Cache de dependencias de Composer
      - name: Cache Composer dependencies
        uses: actions/cache@v4
        with:
          path: vendor
          key: composer-${{ hashFiles('composer.lock') }}
          restore-keys: composer-

      # 4) Instalar dependencias PHP (EN GITHUB)
      - name: Install PHP dependencies
        run: composer install --no-interaction --no-progress --prefer-dist --no-dev --optimize-autoloader

      # 5) Configurar Node 20 para Vite
      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'

      # 6) Cache de node_modules
      - name: Cache node modules
        uses: actions/cache@v4
        with:
          path: node_modules
          key: node-${{ hashFiles('package-lock.json') }}
          restore-keys: node-

      # 7) Instalar dependencias JS
      - name: Install NPM dependencies
        run: npm ci

      # 8) Compilar assets con Vite
      - name: Build assets
        run: npm run build

      # 9) Subir proyecto compilado a EC2 (sin borrar la carpeta => .env se conserva)
      - name: Upload project to EC2 via SCP
        uses: appleboy/scp-action@v0.1.7
        with:
          host: ${{ secrets.EC2_HOST }}
          username: ${{ secrets.EC2_USER }}
          key: ${{ secrets.EC2_SSH_KEY }}
          port: ${{ secrets.EC2_PORT }}
          source: ./
          target: ${{ env.APP_DIR }}
          overwrite: true        # sobreescribe archivos que sí existan en el source
          rm: false              # NO borra la carpeta destino (conserva tu .env)
          strip_components: 0

      # 10) Comandos remotos en EC2 (migraciones, caches, permisos, restart Apache)
      - name: Run remote Laravel commands on EC2
        uses: appleboy/ssh-action@v1.1.0
        with:
          host: ${{ secrets.EC2_HOST }}
          username: ${{ secrets.EC2_USER }}
          key: ${{ secrets.EC2_SSH_KEY }}
          port: ${{ secrets.EC2_PORT }}
          script: |
            cd $APP_DIR

            # Migraciones en producción
            php artisan migrate --force

            # Limpiar y regenerar caches
            php artisan config:clear
            php artisan cache:clear
            php artisan route:clear
            php artisan view:clear

            php artisan config:cache
            php artisan route:cache
            php artisan view:cache

            # Permisos correctos para storage y cache
            sudo chown -R ubuntu:www-data $APP_DIR
            sudo chmod -R 775 storage bootstrap/cache

            # Reiniciar Apache
            sudo systemctl restart apache2

Este pipeline se ejecuta automáticamente en cada push a main o de forma manual con workflow_dispatch. Se asegura de que el build de Laravel y Vite se realice en GitHub, y solo se copian archivos compilados hacia EC2. La opción rm: false garantiza que la carpeta remota no se borre, manteniendo el .env y otros archivos propios del servidor.

Problemas encontrados y cómo se resolvieron

1. Errores en logs y permisos de storage/

Al acceder a rutas de autenticación se recibió un error indicando que storage/logs/laravel.log no podía abrirse.

2. Error de Vite: ViteManifestNotFoundException

Laravel mostraba un error al usar @vite() porque no existía public/build/manifest.json en el servidor.

3. Problemas de SSH y timeouts al copiar archivos

Los primeros intentos de despliegue fallaron con errores del tipo ssh: no key found y timeouts en la conexión al puerto 22.

4. MissingAppKeyException (APP_KEY faltante)

Tras uno de los despliegues, Laravel lanzó Illuminate\Encryption\MissingAppKeyException.

Seguridad y buenas prácticas

Resultados y aprendizajes

Al finalizar, se obtuvo una API Laravel funcional, accesible desde Internet, con un flujo de despliegue estable y automatizado. Algunos aprendizajes clave:

Regresar