Jair Rivera · Blog

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:

  • Entorno Linux (Ubuntu) optimizado para Laravel.
  • Configuración de Apache y VirtualHost apuntando a public/.
  • Base de datos MariaDB con usuario y permisos dedicados creados desde consola SQL.
  • Pipeline de CI/CD que compile Vite y despliegue al servidor sin intervención manual.
  • Manejo correcto de .env, permisos en storage/ y caches de Laravel.

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)
  • Servidor: AWS EC2 con Ubuntu Server 22.04 LTS.
  • Web server: Apache 2, módulo rewrite activado.
  • Aplicación: Laravel (última versión 12.x) con rutas de API y vistas de autenticación.
  • BD: MariaDB local en la misma instancia EC2.

Tecnologías y herramientas

  • Laravel 12 — Framework backend principal.
  • PHP 8.2 — Runtime configurado con extensiones: mbstring, bcmath, intl, pdo_mysql, curl, xml, ctype, json, tokenizer.
  • Composer — Gestión de dependencias PHP.
  • Node.js 20 + npm — Entorno para Vite.
  • Vite — Bundler de assets (genera public/build/manifest.json).
  • MariaDB — Base de datos relacional.
  • Git & GitHub — Control de versiones y remoto.
  • GitHub Actions — Pipeline de CI/CD.
  • SSH (llaves públicas/privadas) — Acceso seguro y despliegue automático.

Preparación del servidor EC2 y VirtualHost de Apache

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

  • Actualización del sistema: sudo apt update && sudo apt upgrade -y.
  • Instalación base: apache2, git, curl, unzip.
  • Instalación de PHP 8.2 y extensiones requeridas.
  • Instalación de MariaDB y configuración con sudo mysql_secure_installation.
  • Clonado del proyecto en /var/www/laravel-api-frutas.

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>
  • Guardado en /etc/apache2/sites-available/laravel-api.conf.
  • Habilitado con sudo a2ensite laravel-api y deshabilitado el sitio por defecto si era necesario.
  • Activado el módulo rewrite: sudo a2enmod rewrite.
  • Reinicio de Apache: sudo systemctl restart apache2.

Configuración de la base de datos

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

  • Ingreso a la consola: sudo mariadb (o mysql -u root -p).
  • Creación de la base de datos para la API: CREATE DATABASE api_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
  • Creación de un usuario específico para la aplicación: CREATE USER 'api_user'@'localhost' IDENTIFIED BY '********';
  • Asignación de permisos: GRANT ALL PRIVILEGES ON api_db.* TO 'api_user'@'localhost';
  • Refresco de privilegios: FLUSH PRIVILEGES;
  • Configuración del .env de Laravel con DB_DATABASE, DB_USERNAME y DB_PASSWORD correctos.
  • Ejecución de migraciones desde el pipeline: php artisan migrate --force.

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.

  • Causa: permisos insuficientes para el usuario del servidor web.
  • Solución: ajustar propietarios y permisos: sudo chown -R ubuntu:www-data /var/www/laravel-api-frutas y sudo chmod -R 775 storage bootstrap/cache.
  • Estos comandos se integraron al script remoto del workflow.

2. Error de Vite: ViteManifestNotFoundException

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

  • Causa: el build de Vite no se había ejecutado en producción.
  • Solución inicial: ejecutar npm install y npm run build manualmente en EC2.
  • Solución definitiva: mover el build al pipeline de GitHub Actions (npm ci + npm run build), de forma que siempre se genere el manifest antes de desplegar.

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.

  • Se generó un par de llaves específico para GitHub Actions: ssh-keygen -t ed25519 -C "github-actions" -f github_actions.
  • La clave pública se añadió en ~/.ssh/authorized_keys del usuario ubuntu en EC2.
  • La clave privada se guardó en el secreto EC2_SSH_KEY del repositorio.
  • Se revisaron los Security Groups para abrir el puerto 22 desde Internet (al menos mientras se terminaba la configuración del pipeline).

4. MissingAppKeyException (APP_KEY faltante)

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

  • Causa: el archivo .env del servidor perdió o no tenía un APP_KEY válido.
  • Solución:
    • Regenerar la clave con php artisan key:generate directamente en EC2.
    • Asegurarse de que .env no está en el repo y se mantiene solo en el servidor.
    • Configurar el step de scp con rm: false para no borrar la carpeta completa del proyecto.

Seguridad y buenas prácticas

  • Uso de llave SSH exclusiva para el runner de GitHub Actions.
  • Variables sensibles manejadas como GitHub Secrets: EC2_HOST, EC2_USER, EC2_PORT, EC2_SSH_KEY.
  • .env no versionado (listado en .gitignore) y mantenido sólo en EC2.
  • Permisos mínimos requeridos en storage/ y bootstrap/cache/.
  • Usuario de BD dedicado solo con permisos sobre el esquema de la API.

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:

  • Configuración completa de un stack Laravel + Apache + PHP 8.2 + MariaDB en EC2.
  • Comprensión práctica de cómo integrar Composer + Vite dentro de un pipeline de GitHub Actions.
  • Manejo de errores típicos de producción: permisos, logs, Vite, APP_KEY y SSH.
  • Separación limpia entre código, configuración sensible y artefactos de build.
  • Base sólida para futuras APIs Laravel desplegadas con el mismo patrón api-laravel-aws-ec2.