Skip to content

6.3. Aplicación completa con estructura de laravel

6.3.1 Estructura de carpetas en local

phpDeployTest/
├── src/
   ├── app/
   ├── config/
   ├── database/
   ├── public/
              ├── index.php
   ├── resources/
   ├── routes/
   ├── storage/
   ├── tests/
   ├── vendor/
   ├── .env
   ├── artisan
   ├── composer.json
   ├── composer.lock
   ├── phpunit.xml

6.3.2 Primer paso será crear el proyecto de laravel

Para crear un proyecto de Laravel, primero debemos asegurarnos de tener Composer instalado en nuestro sistema. Luego, podemos ejecutar el siguiente comando en la terminal:

composer create-project --prefer-dist laravel/laravel .

Advertencia

Si la conexión a internet no tiene el suficiente ancho de banda o el ordenador no es los uficientemente rápido, nos podemos encontrar con un error de timeout. En ese caso, podemos aumentar el tiempo de espera de Composer ejecutando el siguiente comando:

composer config --global process-timeout 2000

Nota

Asegurarse que estamos en la carpeta htmldel contenedor de docker, y que esta carpeta está vacía. Si no lo está, podemos eliminar todo el contenido de la carpeta html y ejecutar el comando anterior.

Una vez realizado si pulsamos el siguiente enlace, deberíamos ver la página de bienvenida de Laravel:

http://localhost:8080

6.3.3 Estructura de carpetas en el servidor remoto

La estructura de carpetas en el servidor remoto será similar a la de local, pero con algunas diferencias. La carpeta public se moverá a htdocs para que sea accesible desde la web. La estructura del servidor remoto será la siguiente:

phpDeployTest/
├── htdocs/
   ├── src/
       ├── app/
       ├── config/
       ├── database/
       ├── resources/
       ├── routes/
       ├── storage/
       ├── tests/
       ├── vendor/
       ├── .env
       ├── artisan
       ├── composer.json
       ├── composer.lock
       ├── phpunit.xml
    ├── index.php
La carpeta public desaparece y su contenido se mueve a htdocs para que sea accesible desde la web.

6.3.4 Despliegue en producción

Previamente, debemos crear una base de datos en el servidor remoto. Neceistamos los datos de conexión para añadirlos a nuestros secrets de GitHub y que el proceso de despliegue pueda crear el archivo .env con los datos de conexión a la base de datos correctamente.

Campo Secreto Descripción Ejemplo
APP_KEY APP_KEY Clave de la aplicación Laravel. base64:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=
APP_URL APP_URL URL de la aplicación en producción. https://test-laravel.infy.uk/
DB_CONNECTION DB_CONNECTION Tipo de conexión a la base de datos. mysql
DB_HOST DB_HOST Host de la base de datos. sql113.infinityfree.com
DB_PORT DB_PORT Puerto de la base de datos. 3306
DB_DATABASE DB_DATABASE Nombre de la base de datos. if0_39075476_laravel
DB_USERNAME DB_USERNAME Usuario de la base de datos. if0_39075476
DB_PASSWORD DB_PASSWORD Contraseña de la base de datos. *********

Importante

Asegúrate de que los datos son correctos y que la base de datos está configurada correctamente. Estos datos son necesarios para que Laravel pueda conectarse a la base de datos en producción. Los datos mostrados son un ejemplo de un servidor de pruebas gratuito, InfinityFree, que permite desplegar aplicaciones web de forma gratuita.

Para realizar el despliegue en producción, utilizaremos GitHub Actions para automatizar el proceso. El archivo deploy.yaml se encargará de subir los archivos al servidor remoto.

El contenido del archivo deploy.yaml es el siguiente:

deploy.yaml

name: Deploy to InfinityFree

on:
   push:
     branches:
       - main

jobs:
  ftp-deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v3

      - name: Set up PHP with composer
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.4'
          extensions: mbstring, intl
          tools: composer

      - name: Install dependencies with composer
        working-directory: ./src
        run: composer install --no-dev --optimize-autoloader

      - name: upload public files to htdocs/
        uses: SamKirkland/FTP-Deploy-Action@4.1.0
        with:
          server: ${{ secrets.FTP_SERVER }}
          username: ${{ secrets.FTP_USERNAME }}
          password: ${{ secrets.FTP_PASSWORD }}
          local-dir: ./src/public/
          server-dir: /htdocs/
          state-name: ftpdeploy-public

      - name: Prepare src without public
        run: |
          mkdir temp_src
          shopt -s extglob
          cp -r src/!(public) temp_src/
        shell: bash

      - name: upload src (except public) to htdocs/src/
        uses: SamKirkland/FTP-Deploy-Action@4.1.0
        with:
          server: ${{ secrets.FTP_SERVER }}
          username: ${{ secrets.FTP_USERNAME }}
          password: ${{ secrets.FTP_PASSWORD }}
          local-dir: ./temp_src/
          server-dir: /htdocs/src/
          state-name: ftpdeploy-src

      - name: Create .env file in temp folder
        run: |
          mkdir temp_env
          echo "APP_NAME=Laravel" > temp_env/.env
          echo "APP_ENV=production" >> temp_env/.env
          echo "APP_KEY=${{ secrets.APP_KEY }}" >> temp_env/.env
          echo "APP_DEBUG=false" >> temp_env/.env
          echo "APP_URL=${{ secrets.APP_URL }}" >> temp_env/.env
          echo "" >> temp_env/.env
          echo "LOG_CHANNEL=stack" >> temp_env/.env
          echo "" >> temp_env/.env
          echo "DB_CONNECTION=${{ secrets.DB_CONNECTION }}" >> temp_env/.env
          echo "DB_HOST=${{ secrets.DB_HOST }}" >> temp_env/.env
          echo "DB_PORT=${{ secrets.DB_PORT }}" >> temp_env/.env
          echo "DB_DATABASE=${{ secrets.DB_DATABASE }}" >> temp_env/.env
          echo "DB_USERNAME=${{ secrets.DB_USERNAME }}" >> temp_env/.env
          echo "DB_PASSWORD=${{ secrets.DB_PASSWORD }}" >> temp_env/.env
          echo "" >> temp_env/.env
          echo "BROADCAST_DRIVER=log" >> temp_env/.env
          echo "CACHE_DRIVER=file" >> temp_env/.env
          echo "FILESYSTEM_DISK=local" >> temp_env/.env
          echo "QUEUE_CONNECTION=sync" >> temp_env/.env
          echo "SESSION_DRIVER=file" >> temp_env/.env
          echo "SESSION_LIFETIME=120" >> temp_env/.env
          echo "" >> temp_env/.env
          echo "MAIL_MAILER=smtp" >> temp_env/.env
          echo "MAIL_HOST=smtp.mailtrap.io" >> temp_env/.env
          echo "MAIL_PORT=2525" >> temp_env/.env
          echo "MAIL_USERNAME=null" >> temp_env/.env
          echo "MAIL_PASSWORD=null" >> temp_env/.env
          echo "MAIL_ENCRYPTION=null" >> temp_env/.env
          echo "MAIL_FROM_ADDRESS=null" >> temp_env/.env
          echo "MAIL_FROM_NAME=\"\${APP_NAME}\"" >> temp_env/.env

      - name: Upload .env file to remote root
        uses: SamKirkland/FTP-Deploy-Action@4.1.0
        with:
          server: ${{ secrets.FTP_SERVER }}
          username: ${{ secrets.FTP_USERNAME }}
          password: ${{ secrets.FTP_PASSWORD }}
          local-dir: ./temp_env/
          server-dir: /htdocs/src/
          include: '.env'

Tambien en GitHub necesitamos crear un token de acceso personal (PAT) con permisos de repo y workflow para que el flujo de trabajo pueda acceder a los secretos y realizar el despliegue.

El token lo guardamos en un secreto llamado PAT_TOKEN. Lo necesitaremos más adelante para realizar el despliegue.

6.3.5 Página principal de laravel

la página index.php de laravel se encuentra en la carpeta public, por lo que al acceder a la URL del servidor remoto deberíamos ver la página de bienvenida de laravel. En el servidor la tendremos en htdocs. Hemos tenido que modificar la página index.php que viene por defecto con laravel para que funcione en nuestro servidor remoto y en local sin tener que mantener dos versiones diferentes.

En este caso el script lo que hace es detectar si estamos en un entorno local o en producción, y cargar el autoloader de Composer y el bootstrap de Laravel desde la ruta correcta. El contenido del archivo index.php es el siguiente:

index.php modificado

<?php
use Illuminate\Foundation\Application;
use Illuminate\Http\Request;

define('LARAVEL_START', microtime(true));

// Detectar entorno local o producción según existencia de carpeta o archivo
if (file_exists(__DIR__ . '/../vendor/autoload.php')) {
    // Estamos en entorno local (estructura estándar Laravel)
    $basePath = realpath(__DIR__ . '/../');
    //echo "Ejecución en local<br>";
} elseif (file_exists(__DIR__ . '/src/vendor/autoload.php')) {
    // Estamos en producción en InfinityFree con estructura modificada
    $basePath = realpath(__DIR__ . '/src/');
    //echo "Ejecución en producción<br>";
} else {
    die("No se ha podido detectar el entorno de ejecución. Path:" . realpath(__DIR__ . '/src/'));
}

// Modo mantenimiento
if (file_exists($maintenance = $basePath . 'storage/framework/maintenance.php')) {
    require $maintenance;
}

// Autoloader de Composer
require $basePath . '/vendor/autoload.php';

// Bootstrap Laravel y manejo de la petición
/** @var Application $app */
$app = require_once $basePath . '/bootstrap/app.php';

// Capturar la petición HTTP y manejarla
$response = $app->handle(
    $request = Request::capture()
);

$response->send();

$app->terminate($request, $response);

6.3.6 Migraciones

Llegados a este punto, tenemos la aplicación desplegada y la base de datos creada. Pero la base de datos está vacía ya que necesitamos ejecutar las migraciones para crear las tablas necesarias. Tenemos dos maneras de hacerlo, la primera manualmente, crear las tablas y datos necesarios en la base de datos remota. Esta la descartamos porque no automatiza nada y está expuest a múltiples errores humanos. La segunda es crear un script que se encargue de ejecutar las migraciones de Laravel de forma automática. Para ello, creamos un archivo migrate.php en la carpeta htdocs con el siguiente contenido:

migrate.php

<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);

use Illuminate\Foundation\Application;
use Illuminate\Console\Application as ArtisanConsole;

require __DIR__ . '/src/vendor/autoload.php';

// Definir la clave para acceder
define('SECRET_KEY', '123456');

// Validar key y acción
$key = $_GET['key'] ?? '';
$action = $_GET['action'] ?? '';

if ($key !== SECRET_KEY) {
    http_response_code(403);
    exit('Acceso denegado.');
}

$allowedActions = ['migrate', 'reset', 'fresh'];

if (!in_array($action, $allowedActions)) {
    http_response_code(400);
    exit('Acción no permitida.');
}

$basePath = __DIR__ . '/src';

// Bootstrap de la aplicación Laravel
$app = require_once $basePath . '/bootstrap/app.php';

$kernel = $app->make(Illuminate\Contracts\Console\Kernel::class);

// Capturar la salida
ob_start();

try {
    switch ($action) {
        case 'migrate':
            $exitCode = $kernel->call('migrate', ['--force' => true]);
            break;
        case 'reset':
            $exitCode = $kernel->call('migrate:reset', ['--force' => true]);
            break;
        case 'fresh':
            $exitCode = $kernel->call('migrate:fresh', ['--force' => true]);
            break;
    }
} catch (Exception $e) {
    ob_end_clean();
    http_response_code(500);
    exit("Error ejecutando comando Artisan: " . $e->getMessage());
}

$output = ob_get_clean();

// Enviar resultado
header('Content-Type: text/plain; charset=utf-8');
echo "Ejecutando comando: $action\n\n";
echo $output;

Este script permite ejecutar las migraciones, con los siguientes parámetros:

  • key: Clave de acceso para ejecutar el script. En este caso, 123456.
  • action: Acción a realizar. Puede ser migrate, reset o fresh. Para ejecutar las migraciones, simplemente accedemos a la URL del script con los parámetros necesarios. Por ejemplo:

http://localhost:8080/migrate.php?key=123456&action=migrate
Esto ejecutará las migraciones en la base de datos configurada en el archivo .env de Laravel. Asegúrate de que la base de datos está configurada correctamente y que los datos son correctos.

Igual que hemos hecho con migrate podemos crar otros scripts o ampliar este para ejecutar comandos de Artisan, como db:seed o cache:clear, siguiendo la misma estructura.

Una vez que hemos ejecutado las migraciones, podemos acceder a la aplicación y ver que las tablas se han creado correctamente en la base de datos. Si todo ha ido bien, deberíamos ver la página de bienvenida de Laravel y no deberíamos tener ningún error.

6.3.7 protección del sitio

El servidor elegido InfinityFree nos obliga a colocar los archivos en la carpeta htdocs. Por lo que quedan expuestos, cosa que va en contra de la filosofia de Laravel. Ahora nos queda pendiente como protecger la carpeta src y su contenido. Solo deben ser accesibles los scripts que se encuentren en la carpeta htdocs. Para ello, podemos crear un archivo .htaccess en la carpeta htdocs con el siguiente contenido:

6.3.8 Resumen.

Hemos creado una aplicación Laravel completa, con su estructura de carpetas y archivos necesarios para su funcionamiento. Hemos realizado el despliegue en un servidor remoto utilizando GitHub Actions y hemos configurado los secretos necesarios para que la aplicación funcione correctamente.

No perdamos de vista el objetivo inicial, que es conseguir que la aplicación que tenemos en local y que estamos desarrollando, se pueda desplegar en un servidor remoto de forma automática y sin necesidad de realizar cambios manuales en el código. Esto nos permitirá tener una aplicación siempre actualizada y lista para ser utilizada por los usuarios.

Aun que nosotros por abreviar hemos desplegado los cambios en la rama main, Lo ideal sería tener una rama de desarrollo y una rama de producción. De esta forma, podemos realizar los cambios en la rama de desarrollo y, una vez que estén listos, hacer un merge a la rama de producción para que se desplieguen automáticamente en el servidor remoto. Pero eso queda para los cursos de git.