En esta publicación vamos a ver como utilizar Docker como entorno de trabajo para Laravel, para ello crearemos un proyecto con Docker Laravel con las siguientes características: framework Laravel 8, base de datos MySQL 5.7 y servidor web Nginx, con estos 3 elementos combinados, con la instalación y configuración de node y composer, y el acceso a artisan montaremos una estructura de trabajo para la creación de proyectos con Docker y Laravel.

Aunque se explican algunos detalles y configuraciones de Docker y Docker Compose es recomendable tener algún conocimiento previo de Docker, por lo demás siguiendo estos pasos montarás tu entorno de manera sencilla.d

Índice

  1. Creación del proyecto Laravel
  2. Creación del Dockerfile de la máquina principal con PHP
  3. Preparación de ficheros de configuración para docker-compose
  4. Creación del fichero docker-compose
  5. Documentación y referencias

Software utilizado

  • Linux (Ubuntu 20.04 LTS): sistema operativo Linux con la versión de Ubuntu 20.04 LTS
  • Docker (docker): Docker version 20.10.7, esta guía se ha generado utilizando esta versión de Docker.
  • Docker compose (docker-compose): docker-compose version 1.25.0, , esta guía se ha generado utilizando esta versión
  • PHP 7.4: en la máquina Docker inicial lo hacemos con una imagen de PHP con la versión 7,4
  • Laravel 8: framewok PHP de desarrollo de aplicaciones web, en este caso utilizaremos la versión 8.
  • Mysql 5.7: versión utilizada en la imagen de docker-compose para la base de datos.
  • Nginx: servidor web de aplicaciones
  • PHP 7.4: utilizamos PHP en una versión superior a 7.4 siguiendo las recomendaciones de Laravel, por eso la imagen que utilicemos será de esta versión.
  • Composer 2: gestor de paquetes a partir de archivos json utilizado por Laravel, utilizamos Composer 2 porque lo necesitamos para Laravel 8, en esta publicación indicamos como actualizarlo.
  • Artisan: programa de línea de comandos de Laravel que nos ayuda en la creación de elementos como modelos, controladores, …, y también, nos proporciona otras utilidades como gestión de las migraciones o el listado de las rutas.

Lo primero que vamos a hacer es crear un carpeta donde organizaremos toda la estructura de proyecto y de trabajo, en mi caso la he llamado: cx-lcustomerdb-app

1. Creación del proyecto Laravel

En primer lugar crearemos el proyecto Laravel en nuestro entorno de trabajo dentro de la carpeta que hemos creado, para ello utilizaremos composer, para este proyecto necesitareis composer 2, si ya lo teneis perfecto, en caso contrario seguid los pasos que indico en el siguiente punto.

La creación del proyecto con Laravel es la misma que utilizaríamos si crearamos un proyecto Laravel para después cargarlo en un servidor instalado en nuestro sistema operativo .

Instalación y actualización de composer 2

Para la última versión de Laravel necesitaremos composer 2, vamos a explicar como instalarlo, la versión por defecto en los repositorios Linux en el momento de realizar este tutorial es la versión 1. La actualización la puedes hacer siguiento estes pasos, si quieres más información la tienes en este enlace: https://getcomposer.org/download/

Para la actualización empezaremos desinstalando la versión 1 si la tenemos, a continuación, con php utilizaremos las opciones para la descarga con el fichero composer-setup.php que almacenamos en un fichero temporal, finalmente procedemos a su instalación en la ubicación: /usr/local/bin y listo:

 sudo apt-get remove composer
 
 php -r "copy('https://getcomposer.org/installer', '/tmp/composer-setup.php');"
 
 php -r "if (hash_file('sha384', '/tmp/composer-setup.php') === '756890a4488ce9024fc62c56153228907f1545c228516cbf63f885e036d37e9a59d27d63f46af1d4d07ee0f76181c7d3') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('/tmp/composer-setup.php'); } echo PHP_EOL;"
 
 sudo php /tmp/composer-setup.php --install-dir=/usr/local/bin --filename=composer
 
 php -r "unlink('composer-setup.php');"

Creación del proyecto con Laravel con composer

La estructura con composer para crear un proyecto con Laravel es sencilla, primero el comando composer a continuación el comando create-project seguido del tipo de proyecto Laravel laravel/laravel por último el nombre del proyecto:

composer create-project laravel/laravel cx-lcustomerdb

Al ejecutar la creación del proyecto composer nos muestra como salida la versión de Laravel instalada, y todos los paquetes asociados a Laravel con sus versiones que se instalan simultáneamente, si quieres ver este ejemplo, puedes hacerlo desplegando el código:

Creating a "laravel/laravel" project at "./cx-lcustomerdb"
 Installing laravel/laravel (v8.5.22)
 Installing laravel/laravel (v8.5.22): Extracting archive
 Created project in /home/xules/xulprocx-local/DOCKER-Projects/Docker-Laravel/cx-laravel-customerdb/cx-lcustomerdb 
   @php -r "file_exists('.env') || copy('.env.example', '.env');"
   Loading composer repositories with package information
   Updating dependencies
   Lock file operations: 105 installs, 0 updates, 0 removals
      Locking asm89/stack-cors (v2.0.3)
   Locking brick/math (0.9.2)
   Locking doctrine/inflector (2.0.3)
   Locking doctrine/instantiator (1.4.0)
   Locking doctrine/lexer (1.2.1)
   Locking dragonmantank/cron-expression (v3.1.0)
   Locking egulias/email-validator (2.1.25)
   Locking facade/flare-client-php (1.8.1)
   Locking facade/ignition (2.11.0)
   Locking facade/ignition-contracts (1.0.2)
   Locking fakerphp/faker (v1.15.0)
   Locking fideloper/proxy (4.4.1)
   Locking filp/whoops (2.14.0)
   Locking fruitcake/laravel-cors (v2.0.4)
   Locking graham-campbell/result-type (v1.0.1)
   Locking guzzlehttp/guzzle (7.3.0)
   Locking guzzlehttp/promises (1.4.1)
   Locking guzzlehttp/psr7 (2.0.0)
   Locking hamcrest/hamcrest-php (v2.0.1)
   Locking laravel/framework (v8.50.0)
   Locking laravel/sail (v1.8.5)
   Locking laravel/tinker (v2.6.1)
   Locking league/commonmark (1.6.5)
   Locking league/flysystem (1.1.4)
   Locking league/mime-type-detection (1.7.0)
   Locking mockery/mockery (1.4.3)
   Locking monolog/monolog (2.3.1)
   Locking myclabs/deep-copy (1.10.2)
   Locking nesbot/carbon (2.50.0)
   Locking nikic/php-parser (v4.11.0)
   Locking nunomaduro/collision (v5.5.0)
   Locking opis/closure (3.6.2)
   Locking phar-io/manifest (2.0.1)
   Locking phar-io/version (3.1.0)
   Locking phpdocumentor/reflection-common (2.2.0)
   Locking phpdocumentor/reflection-docblock (5.2.2)
   Locking phpdocumentor/type-resolver (1.4.0)
   Locking phpoption/phpoption (1.7.5)
   Locking phpspec/prophecy (1.13.0)
   Locking phpunit/php-code-coverage (9.2.6)
   Locking phpunit/php-file-iterator (3.0.5)
   Locking phpunit/php-invoker (3.1.1)
   Locking phpunit/php-text-template (2.0.4)
   Locking phpunit/php-timer (5.0.3)
   Locking phpunit/phpunit (9.5.6)
   Locking psr/container (1.1.1)
   Locking psr/event-dispatcher (1.0.0)
   Locking psr/http-client (1.0.1)
   Locking psr/http-factory (1.0.1)
   Locking psr/http-message (1.0.1)
   Locking psr/log (1.1.4)
   Locking psr/simple-cache (1.0.1)
   Locking psy/psysh (v0.10.8)
   Locking ralouphie/getallheaders (3.0.3)
   Locking ramsey/collection (1.1.3)
   Locking ramsey/uuid (4.1.1)
   Locking sebastian/cli-parser (1.0.1)
   Locking sebastian/code-unit (1.0.8)
   Locking sebastian/code-unit-reverse-lookup (2.0.3)
   Locking sebastian/comparator (4.0.6)
   Locking sebastian/complexity (2.0.2)
   Locking sebastian/diff (4.0.4)
   Locking sebastian/environment (5.1.3)
   Locking sebastian/exporter (4.0.3)
   Locking sebastian/global-state (5.0.3)
   Locking sebastian/lines-of-code (1.0.3)
   Locking sebastian/object-enumerator (4.0.4)
   Locking sebastian/object-reflector (2.0.4)
   Locking sebastian/recursion-context (4.0.4)
   Locking sebastian/resource-operations (3.0.3)
   Locking sebastian/type (2.3.4)
   Locking sebastian/version (3.0.2)
   Locking swiftmailer/swiftmailer (v6.2.7)
   Locking symfony/console (v5.3.2)
   Locking symfony/css-selector (v5.3.0)
   Locking symfony/deprecation-contracts (v2.4.0)
   Locking symfony/error-handler (v5.3.3)
   Locking symfony/event-dispatcher (v5.3.0)
   Locking symfony/event-dispatcher-contracts (v2.4.0)
   Locking symfony/finder (v5.3.0)
   Locking symfony/http-client-contracts (v2.4.0)
   Locking symfony/http-foundation (v5.3.3)
   Locking symfony/http-kernel (v5.3.3)
   Locking symfony/mime (v5.3.2)
   Locking symfony/polyfill-ctype (v1.23.0)
   Locking symfony/polyfill-iconv (v1.23.0)
   Locking symfony/polyfill-intl-grapheme (v1.23.0)
   Locking symfony/polyfill-intl-idn (v1.23.0)
   Locking symfony/polyfill-intl-normalizer (v1.23.0)
   Locking symfony/polyfill-mbstring (v1.23.0)
   Locking symfony/polyfill-php72 (v1.23.0)
   Locking symfony/polyfill-php73 (v1.23.0)
   Locking symfony/polyfill-php80 (v1.23.0)
   Locking symfony/process (v5.3.2)
   Locking symfony/routing (v5.3.0)
   Locking symfony/service-contracts (v2.4.0)
   Locking symfony/string (v5.3.3)
   Locking symfony/translation (v5.3.3)
   Locking symfony/translation-contracts (v2.4.0)
   Locking symfony/var-dumper (v5.3.3)
   Locking theseer/tokenizer (1.2.0)
   Locking tijsverkoyen/css-to-inline-styles (2.2.3)
   Locking vlucas/phpdotenv (v5.3.0)
   Locking voku/portable-ascii (1.5.6)
   Locking webmozart/assert (1.10.0)
   Writing lock file
   Installing dependencies from lock file (including require-dev)
   Package operations: 105 installs, 0 updates, 0 removals
   Downloading doctrine/inflector (2.0.3)
   Downloading doctrine/lexer (1.2.1)
   Downloading symfony/polyfill-ctype (v1.23.0)
   Downloading webmozart/assert (1.10.0)
   Downloading dragonmantank/cron-expression (v3.1.0)
   Downloading symfony/polyfill-php80 (v1.23.0)
   Downloading symfony/polyfill-mbstring (v1.23.0)
   Downloading symfony/var-dumper (v5.3.3)
   Downloading symfony/polyfill-intl-normalizer (v1.23.0)
   Downloading symfony/polyfill-intl-grapheme (v1.23.0)
   Downloading symfony/string (v5.3.3)
   Downloading psr/container (1.1.1)
   Downloading symfony/service-contracts (v2.4.0)
   Downloading symfony/polyfill-php73 (v1.23.0)
   Downloading symfony/deprecation-contracts (v2.4.0)
   Downloading symfony/console (v5.3.2)
   Downloading psr/log (1.1.4)
   Downloading monolog/monolog (2.3.1)
   Downloading voku/portable-ascii (1.5.6)
   Downloading phpoption/phpoption (1.7.5)
   Downloading graham-campbell/result-type (v1.0.1)
   Downloading vlucas/phpdotenv (v5.3.0)
   Downloading symfony/css-selector (v5.3.0)
   Downloading tijsverkoyen/css-to-inline-styles (2.2.3)
   Downloading symfony/routing (v5.3.0)
   Downloading symfony/process (v5.3.2)
   Downloading symfony/polyfill-php72 (v1.23.0)
   Downloading symfony/polyfill-intl-idn (v1.23.0)
   Downloading symfony/mime (v5.3.2)
   Downloading symfony/http-foundation (v5.3.3)
   Downloading symfony/http-client-contracts (v2.4.0)
   Downloading psr/event-dispatcher (1.0.0)
   Downloading symfony/event-dispatcher-contracts (v2.4.0)
   Downloading symfony/event-dispatcher (v5.3.0)
   Downloading symfony/error-handler (v5.3.3)
   Downloading symfony/http-kernel (v5.3.3)
   Downloading symfony/finder (v5.3.0)
   Downloading symfony/polyfill-iconv (v1.23.0)
   Downloading egulias/email-validator (2.1.25)
   Downloading swiftmailer/swiftmailer (v6.2.7)
   Downloading ramsey/collection (1.1.3)
   Downloading brick/math (0.9.2)
   Downloading ramsey/uuid (4.1.1)
   Downloading psr/simple-cache (1.0.1)
   Downloading opis/closure (3.6.2)
   Downloading symfony/translation-contracts (v2.4.0)
   Downloading symfony/translation (v5.3.3)
   Downloading nesbot/carbon (2.50.0)
   Downloading league/mime-type-detection (1.7.0)
   Downloading league/flysystem (1.1.4)
   Downloading league/commonmark (1.6.5)
   Downloading laravel/framework (v8.50.0)
   Downloading facade/ignition-contracts (1.0.2)
   Downloading facade/flare-client-php (1.8.1)
   Downloading facade/ignition (2.11.0)
   Downloading fakerphp/faker (v1.15.0)
   Downloading fideloper/proxy (4.4.1)
   Downloading asm89/stack-cors (v2.0.3)
   Downloading fruitcake/laravel-cors (v2.0.4)
   Downloading psr/http-message (1.0.1)
   Downloading psr/http-client (1.0.1)
   Downloading ralouphie/getallheaders (3.0.3)
   Downloading psr/http-factory (1.0.1)
   Downloading guzzlehttp/psr7 (2.0.0)
   Downloading guzzlehttp/promises (1.4.1)
   Downloading guzzlehttp/guzzle (7.3.0)
   Downloading laravel/sail (v1.8.5)
   Downloading nikic/php-parser (v4.11.0)
   Downloading psy/psysh (v0.10.8)
   Downloading laravel/tinker (v2.6.1)
   Downloading hamcrest/hamcrest-php (v2.0.1)
   Downloading mockery/mockery (1.4.3)
   Downloading filp/whoops (2.14.0)
   Downloading nunomaduro/collision (v5.5.0)
   Downloading phpdocumentor/reflection-common (2.2.0)
   Downloading phpdocumentor/type-resolver (1.4.0)
   Downloading phpdocumentor/reflection-docblock (5.2.2)
   Downloading sebastian/version (3.0.2)
   Downloading sebastian/type (2.3.4)
   Downloading sebastian/resource-operations (3.0.3)
   Downloading sebastian/recursion-context (4.0.4)
   Downloading sebastian/object-reflector (2.0.4)
   Downloading sebastian/object-enumerator (4.0.4)
   Downloading sebastian/global-state (5.0.3)
   Downloading sebastian/exporter (4.0.3)
   Downloading sebastian/environment (5.1.3)
   Downloading sebastian/diff (4.0.4)
   Downloading sebastian/comparator (4.0.6)
   Downloading sebastian/code-unit (1.0.8)
   Downloading sebastian/cli-parser (1.0.1)
   Downloading phpunit/php-timer (5.0.3)
   Downloading phpunit/php-text-template (2.0.4)
   Downloading phpunit/php-invoker (3.1.1)
   Downloading phpunit/php-file-iterator (3.0.5)
   Downloading theseer/tokenizer (1.2.0)
   Downloading sebastian/lines-of-code (1.0.3)
   Downloading sebastian/complexity (2.0.2)
   Downloading sebastian/code-unit-reverse-lookup (2.0.3)
   Downloading phpunit/php-code-coverage (9.2.6)
   Downloading doctrine/instantiator (1.4.0)
   Downloading phpspec/prophecy (1.13.0)
   Downloading phar-io/version (3.1.0)
   Downloading phar-io/manifest (2.0.1)
   Downloading myclabs/deep-copy (1.10.2)
   Downloading phpunit/phpunit (9.5.6)
   Installing doctrine/inflector (2.0.3): Extracting archive
   Installing doctrine/lexer (1.2.1): Extracting archive
   Installing symfony/polyfill-ctype (v1.23.0): Extracting archive
   Installing webmozart/assert (1.10.0): Extracting archive
   Installing dragonmantank/cron-expression (v3.1.0): Extracting archive
   Installing symfony/polyfill-php80 (v1.23.0): Extracting archive
   Installing symfony/polyfill-mbstring (v1.23.0): Extracting archive
   Installing symfony/var-dumper (v5.3.3): Extracting archive
   Installing symfony/polyfill-intl-normalizer (v1.23.0): Extracting archive
   Installing symfony/polyfill-intl-grapheme (v1.23.0): Extracting archive
   Installing symfony/string (v5.3.3): Extracting archive
   Installing psr/container (1.1.1): Extracting archive
   Installing symfony/service-contracts (v2.4.0): Extracting archive
   Installing symfony/polyfill-php73 (v1.23.0): Extracting archive
   Installing symfony/deprecation-contracts (v2.4.0): Extracting archive
   Installing symfony/console (v5.3.2): Extracting archive
   Installing psr/log (1.1.4): Extracting archive
   Installing monolog/monolog (2.3.1): Extracting archive
   Installing voku/portable-ascii (1.5.6): Extracting archive
   Installing phpoption/phpoption (1.7.5): Extracting archive
   Installing graham-campbell/result-type (v1.0.1): Extracting archive
   Installing vlucas/phpdotenv (v5.3.0): Extracting archive
   Installing symfony/css-selector (v5.3.0): Extracting archive
   Installing tijsverkoyen/css-to-inline-styles (2.2.3): Extracting archive
   Installing symfony/routing (v5.3.0): Extracting archive
   Installing symfony/process (v5.3.2): Extracting archive
   Installing symfony/polyfill-php72 (v1.23.0): Extracting archive
   Installing symfony/polyfill-intl-idn (v1.23.0): Extracting archive
   Installing symfony/mime (v5.3.2): Extracting archive
   Installing symfony/http-foundation (v5.3.3): Extracting archive
   Installing symfony/http-client-contracts (v2.4.0): Extracting archive
   Installing psr/event-dispatcher (1.0.0): Extracting archive
   Installing symfony/event-dispatcher-contracts (v2.4.0): Extracting archive
   Installing symfony/event-dispatcher (v5.3.0): Extracting archive
   Installing symfony/error-handler (v5.3.3): Extracting archive
   Installing symfony/http-kernel (v5.3.3): Extracting archive
   Installing symfony/finder (v5.3.0): Extracting archive
   Installing symfony/polyfill-iconv (v1.23.0): Extracting archive
   Installing egulias/email-validator (2.1.25): Extracting archive
   Installing swiftmailer/swiftmailer (v6.2.7): Extracting archive
   Installing ramsey/collection (1.1.3): Extracting archive
   Installing brick/math (0.9.2): Extracting archive
   Installing ramsey/uuid (4.1.1): Extracting archive
   Installing psr/simple-cache (1.0.1): Extracting archive
   Installing opis/closure (3.6.2): Extracting archive
   Installing symfony/translation-contracts (v2.4.0): Extracting archive
   Installing symfony/translation (v5.3.3): Extracting archive
   Installing nesbot/carbon (2.50.0): Extracting archive
   Installing league/mime-type-detection (1.7.0): Extracting archive
   Installing league/flysystem (1.1.4): Extracting archive
   Installing league/commonmark (1.6.5): Extracting archive
   Installing laravel/framework (v8.50.0): Extracting archive
   Installing facade/ignition-contracts (1.0.2): Extracting archive
   Installing facade/flare-client-php (1.8.1): Extracting archive
   Installing facade/ignition (2.11.0): Extracting archive
   Installing fakerphp/faker (v1.15.0): Extracting archive
   Installing fideloper/proxy (4.4.1): Extracting archive
   Installing asm89/stack-cors (v2.0.3): Extracting archive
   Installing fruitcake/laravel-cors (v2.0.4): Extracting archive
   Installing psr/http-message (1.0.1): Extracting archive
   Installing psr/http-client (1.0.1): Extracting archive
   Installing ralouphie/getallheaders (3.0.3): Extracting archive
   Installing psr/http-factory (1.0.1): Extracting archive
   Installing guzzlehttp/psr7 (2.0.0): Extracting archive
   Installing guzzlehttp/promises (1.4.1): Extracting archive
   Installing guzzlehttp/guzzle (7.3.0): Extracting archive
   Installing laravel/sail (v1.8.5): Extracting archive
   Installing nikic/php-parser (v4.11.0): Extracting archive
   Installing psy/psysh (v0.10.8): Extracting archive
   Installing laravel/tinker (v2.6.1): Extracting archive
   Installing hamcrest/hamcrest-php (v2.0.1): Extracting archive
   Installing mockery/mockery (1.4.3): Extracting archive
   Installing filp/whoops (2.14.0): Extracting archive
   Installing nunomaduro/collision (v5.5.0): Extracting archive
   Installing phpdocumentor/reflection-common (2.2.0): Extracting archive
   Installing phpdocumentor/type-resolver (1.4.0): Extracting archive
   Installing phpdocumentor/reflection-docblock (5.2.2): Extracting archive
   Installing sebastian/version (3.0.2): Extracting archive
   Installing sebastian/type (2.3.4): Extracting archive
   Installing sebastian/resource-operations (3.0.3): Extracting archive
   Installing sebastian/recursion-context (4.0.4): Extracting archive
   Installing sebastian/object-reflector (2.0.4): Extracting archive
   Installing sebastian/object-enumerator (4.0.4): Extracting archive
   Installing sebastian/global-state (5.0.3): Extracting archive
   Installing sebastian/exporter (4.0.3): Extracting archive
   Installing sebastian/environment (5.1.3): Extracting archive
   Installing sebastian/diff (4.0.4): Extracting archive
   Installing sebastian/comparator (4.0.6): Extracting archive
   Installing sebastian/code-unit (1.0.8): Extracting archive
   Installing sebastian/cli-parser (1.0.1): Extracting archive
   Installing phpunit/php-timer (5.0.3): Extracting archive
   Installing phpunit/php-text-template (2.0.4): Extracting archive
   Installing phpunit/php-invoker (3.1.1): Extracting archive
   Installing phpunit/php-file-iterator (3.0.5): Extracting archive
   Installing theseer/tokenizer (1.2.0): Extracting archive
   Installing sebastian/lines-of-code (1.0.3): Extracting archive
   Installing sebastian/complexity (2.0.2): Extracting archive
   Installing sebastian/code-unit-reverse-lookup (2.0.3): Extracting archive
   Installing phpunit/php-code-coverage (9.2.6): Extracting archive
   Installing doctrine/instantiator (1.4.0): Extracting archive
   Installing phpspec/prophecy (1.13.0): Extracting archive
   Installing phar-io/version (3.1.0): Extracting archive
   Installing phar-io/manifest (2.0.1): Extracting archive
   Installing myclabs/deep-copy (1.10.2): Extracting archive
   Installing phpunit/phpunit (9.5.6): Extracting archive
   76 package suggestions were added by new dependencies, use <code>composer suggest</code> to see details.
   Generating optimized autoload files
   Illuminate\Foundation\ComposerScripts::postAutoloadDump
   @php artisan package:discover --ansi
   Discovered Package: facade/ignition
   Discovered Package: fideloper/proxy
   Discovered Package: fruitcake/laravel-cors
   Discovered Package: laravel/sail
   Discovered Package: laravel/tinker
   Discovered Package: nesbot/carbon
   Discovered Package: nunomaduro/collision
   Package manifest generated successfully.
   74 packages you are using are looking for funding.
   Use the <code>composer fund</code> command to find out more!
   @php artisan key:generate --ansi
   Application key set successfully.    

2. Creación del Dockerfile de la máquina principal con PHP

Para la creación del fichero Dockerfile nos situamos dentro de la carpeta que hemos creado al crear el proyecto Laravel, en mi caso cx-lcustomerdb, una vez dentro creamos el fichero Dockerfile (este fichero se crea sin extensión de tipo de archivo) veremos paso a paso su contenido explicando los comandos y utilidades, partimos de una imagen php con la versión 7.4 para ello utilizamos la imagen disponible: 7.4-fpm, con la etiqueta FROM:

FROM php:7.4-fpm

Permitimos el paso de parámetros (argumentos) que se definirán en el fichero docker-compose.yml, para ello utilizamos la etiqueta ARG y a continuación el nombre de los campos, dentro del fichero docker-compose.yml nos referiremos a ellos por ese nombre:

FROM php:7.4-fpm
 # Permitimos el paso de parámetros (argumentos) que se definirán en el fichero docker-compose.yml
 ARG user
 ARG uid

Completamos la instalación de la imagen PHP con dependencias y utilidades a nivel de sistema operativo como: git, curl y zip, esto lo añadimos en el fichero a continuación de las instrucciones anteriores.

Para la ejecución de apt-get dentro de la imagen usamos el comando RUN que nos permite ejecutar un comando dentro de la máquina virtual en este caso para realizar la instalación de paquetes, en primer lugar actualizamos el sistema y seguimos con la instalación de las dependencias, una vez finalizado borramos cache y limpiamos los archivos de instalación:

  # Añadimos dependencias y utilidades interesantes al sistema como: git, curl, zip, …:
 RUN apt-get update apt-get install -y \
     git \
     curl \
     libxml2-dev \
     libonig-dev \
     libpng-dev \
     zip \
     unzip
 # Una vez finalizado borramos cache y limpiamos los archivos de instalación
 RUN apt-get clean rm -rf /var/lib/apt/lists/*

Necesitaremos instalara las dependencias y extensiones PHP que utilicemos como: pdo_mysql o mbstring:

 #Instalamos las dependencias y extensiones PHP que necesitaremos en nuestro proyecto como: pdo_mysql o mbstring
 RUN docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd sockets 

Instalamos dentro de la imagen la última versión de composer, para ello copiamos la imagen disponible en el repositorio:

 # Instalamos dentro de la imagen la última versión de composer, para ello copiamos la imagen disponible en el repositorio:
 COPY --from=composer:2.0.13 /usr/bin/composer /usr/bin/composer

Copiamos de la última imagen de node en nuestro proyecto con las librerías de los módulos y de node, después creamos un enlace virtual para poder utilizar directamente el comando npm, realizado esto ya podemos ejecutar la instalación con el comando RUN para ejecutar npm install.

 # Copiamos de la última imagen de node en nuestro proyecto las librerías de los módulos y de node
 COPY --from=node:latest /usr/local/lib/node_modules /usr/local/lib/node_modules
 COPY --from=node:latest /usr/local/bin/node /usr/local/bin/node
 # Creamos un enlace virtual para poder utilizar directamente npm dentro de la máquina Docker:
 RUN ln -s /usr/local/lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm

Por último, asignamos permisos al usuario y lo añadimos a los grupos: www-data y root, configuramos para poder ejecutar directamente composer en nuestra máquina, e indicamos el directorio de trabajo, está es una forma sencilla de tener disponible en línea de comandos composer y artisan. Hay gente que prefiere llevar estos dos elementos a sus respectivas imágenes, pero en mi opinión la configuración que hacemos aquí es más sencilla, al integrar estos elementos en la imagen que creamos con el fichero Dockerfile. Estos son los datos de la última parte del fichero:

 # Creamos un usuario de sistema para ejecutar los comando Composer y Artisan:
 RUN useradd -G www-data,root -u $uid -d /home/$user $user
 RUN mkdir -p /home/$user/.composer \
     chown -R $user:$user /home/$user
 
 # Definimos el directorio de trabajo dentro de nuestra imagen
 WORKDIR /var/www
 USER $user

Este es el resultado final completo de nuestro fichero Dockerfile:

FROM php:7.4-fpm

# Permitimos el paso de parámetros (argumentos) que se definirán en el fichero docker-compose.yml
ARG user
ARG uid

# Añadimos dependencias y utilidades interesantes al sistema como: git, curl, zip, ...:
RUN apt-get update apt-get install -y \
    git \
    curl \
    libxml2-dev \
    libonig-dev \
    libpng-dev \
    zip \
    unzip

# Una vez finalizado borramos cache y limpiamos los archivos de instalación
RUN apt-get clean rm -rf /var/lib/apt/lists/*

# Instalamos las dependencias y extensiones PHP que necesitaremos en nuestro proyecto como: pdo_mysql o mbstring
RUN docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd sockets 

# Instalamos dentro de la imagen la última versión de composer, para ello copiamos la imagen disponible en el repositorio:
COPY --from=composer:2.0.13 /usr/bin/composer /usr/bin/composer

# Copiamos de la última imagen de node en nuestro proyecto las librerías de los módulos y de node
COPY --from=node:latest /usr/local/lib/node_modules /usr/local/lib/node_modules
COPY --from=node:latest /usr/local/bin/node /usr/local/bin/node
# Creamos un enlace virtual para poder utilizar directamente npm dentro de la máquina Docker:
RUN ln -s /usr/local/lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm


# Creamos un usuario de sistema para ejecutar los comando Composer y Artisan:
RUN useradd -G www-data,root -u $uid -d /home/$user $user
RUN mkdir -p /home/$user/.composer && \
    chown -R $user:$user /home/$user

# Definimos el directorio de trabajo dentro de nuestra imagen
WORKDIR /var/www

USER $user

La instalación de composer y npm la realizaremos con docker-compose una vez que hayamos lanzado los contenedores de nuestra aplicación.

3. Preparación de ficheros de configuración para docker-compose

Para la puesta en marcha de nuestro proyecto utilizaremos docker-compose, antes de su definición vamos a crear el fichero de configuración para Nginx, y también, indicar como hacer una carga previa sobre la base datos al lanzar el entorno.

Los ficheros de creación que vamos a generar a continuación los creamos dentro del proyecto de Laravel creando una carpeta propia a la que he llamado docker-compose, dentro de esta creamos dos carpetas nuevas, una para nginx y otra para mysql

Nginx fichero de configuración

Vamos a utilizar dentro del fichero docker-compose un fichero de configuración para el servidor Nginx, en este fichero se define el puerto de escucha los ficheros index, y de log, está es una configuracion estándar de un servidor Nginx, por ello no entraremos en más detalles.

Creamos el fichero cx-lcustomer-server.conf dentro de la carpeta nginx creada anteroirmente y con la siguiente configuración:

server {
     listen 80;
     index index.php index.html;
     error_log  /var/log/nginx/error.log;
     access_log /var/log/nginx/access.log;
     root /var/www/public;
     location ~ .php$ {
         try_files $uri =404;
         fastcgi_split_path_info ^(.+.php)(/.+)$;
         fastcgi_pass app:9000;
         fastcgi_index index.php;
         include fastcgi_params;
         fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
         fastcgi_param PATH_INFO $fastcgi_path_info;
     }
     location / {
         try_files $uri $uri/ /index.php?$query_string;
         gzip_static on;
     }
 }

Cuando definamos la imagen del servidor web Nginx dentro del fichero docker-compose veremos como cargar el archivo para que la imagen lo tenga en cuenta y lo procese.

Base de datos entrada de datos inicial

Si queremos carga una base de datos inicial con tablas lo que hacemos es generar un script SQL que pondremos en la carpeta mysql (nombre-proyecto-laravel/docker-compose/mysql).

En mi caso en los proyectos que uso con Laravel voyva utilizar el ORM con las migraciones para la creación y actualización de las tablas no suelo utilizar está opción, no obstante al crear el fichero docker-compose en la imagen mysql indicaré como se definiría.

4. Definición del fichero con Docker Compose

Comentaremos por encima que es Docker Compose para la gente que este empezando, y después veremos los servicios uno a uno explicando las definiciones utilizadas.

¿Qué es Docker Compose?

Compose para Docker es una herramienta que nos permite definir y correr aplicaciones con multi contenedores, una vez definida la configuración mediante el comando docker-compose y sus parámetros podemos crear y lanzar las aplicaciones de forma conjunta y con una línea de comandos.

Además Compose nos permite trabajar con diferentes entornos como Production, Staging, Development y Testing, permitiendo crear un entorno de integración continua.

La configuración de Compose se realiza en un fichero con formato YAML, con la extensión yml, es decir, todos nuestros ficheros de configuración se denominarán: docker-compose.yml.

YAML fue creado bajo la creencia de que todos los datos pueden ser representados adecuadamente como combinaciones de listas, hashes y datos escalares, la sintaxis es relativamente sencilla y muy legible, algunas características de este fichero son:

  • Los contenidos en YAML se describen utilizando el conjunto de caracteres imprimibles de Unicode, bien en UTF-8 o UTF-16.
  • La estructura del documento se denota indentando con espacios en blanco y no se permite el uso de caracteres de tabulación para sangrar.
  • Los miembros de las listas se denotan encabezados por un guion ( – ) con un miembro por cada línea, …
  • Para ver más detalles puedes consultar algiunas características adiciones en la Wikipedia: Wiki YAML Características

Cómo nos indican en la documentación oficial todo fichero de docker se puede establecer en 3 pasos:

  1. Creacion del fichero Dockerfile de nuestra aplicación
  2. Creación de los servicios en el fichero docker-compose.yml con las indicaciones para la construcción y lanzamiento de nuestra aplicación.
  3. Finalmente ya podremos utilizar el comando docker-compose up para crear y lanzar nuestra aplicación con multi contenedores.

Definimos el fichero de nuestra aplicación Laravel

En nuestro fichero de configuración de docker-compose vamos a definir 3 servicios y 1 red, los servicios que definiremo serán:

  • app: en este servicio instalamos una imagen con PHP en concreto la versión php:7.4-fpm, puedes consultar en el Docker Hub de PHP todas las versiones disponibles. La variante que se utiliza aquí correspondiente a php:<version>-fpm es una variante la cual contiene una implementación FastCGI para PHP, esto implica que necesitaremos usar algún tipo de proxy inverso, como Apache o Nginx que comprenda el protocolo FastCGI).
  • db: este servicio lo vamos a dedicar a nuestra base de datos MySQL para ello partiremos de la imagen: mysql:5.7, en este punto puede utilizar otra versión, u otra base de datos como MariaDB, utilizaremos los parámetros para la creación de los usuarios, claves y base de datos inicial, la definición por defecto de Laravel en el fichero .env , veremos como.
  • nginx: nuestro servidor de aplicaciones web donde lanzaremos nuestra aplicación, utilizamos como ya hemos visto una configuración estándar, lo único que necesitamos activar el servicio fastcgi al utilizar la imagen php:7.4-fpm

Como red definimos net-cx-lcustomerdb que tiene una configuración básica en modo bridge como veremos asociaremos todos los servicios dentro de la misma red para que estén interconectados.

La estructura inicial del fichero sobre el que iremos añadiendo la configuración a los servicios es la siguiente, donde ya definimos la configuración de red:

version: "3"
 services:
   app:
     build:
       dockerfile: Dockerfile
     image: cx-lcustomerdb
     ……….
     networks:
       - net-cx-lcustomerdb
   db:
     image: mysql:5.7
     container_name: cx-lcustomerdb-db
     ……….
     networks:
       - net-cx-lcustomerdb
 nginx:
     image: nginx:alpine
     container_name: cx-lcustomerdb-nginx
     ……….
     networks:
       - net-cx-lcustomerdb
 networks:
   net-cx-lcustomerdb:
     driver: bridge

Después de explicar la estructura base de nuestra aplicación con docker-compose vamos a ir viendo paso a paso la definición de cada uno de los servicios.

Servicio app: aplicación Laravel

En el apartado 2 hemos definido el fichero Dockerfile con la estructura base de nuestra aplicación, a partir de una imagen base de php , donde definimos 2 parámetros user y uid.

Paso 1: construcción de la imagen a partir del fichero Dockerfile para ello utilizamos la etiqueta build, y dentro de ella:

  • args: para pasar los parámetros al Dockerfile, dentro le damos los valores a user y uid, en mi caso xules y 1000 respectivamente
  • context: ./ , indicamos que es el directorio actual
  • dockerfile: Dockerfile, indicamos el nombre del fichero que utilizaremos para la construcción de la imagen.

Paso 2: le damos un nombre a nuestra imagen y a nuestro contenedor con los parámetros:

  • image: nombre de la imagen , o en otros casos se utiliza para definir la imagen base que utilizaremos para la construcción del contenedor:
  • container_name: nombre del contenedor, esto nos servirá para acceder a él por el nombre, y para referenciarlo en las visualizaciones y comandos docker.
  • restart: unless-stopped, con este comando definimos que una vez lanzada nuestra aplicación está este siempre activa a no ser que se paré a propósito.

Paso 3: definición del directorio de trabajo y del volumen:

  • working_dir: definimos el directorio de trabajo para nuestro contenedor: /var/www/
  • volumes: un volumen es un espacio de almacenamiento externo al contenedor que nos permite tener persistencia de los datos con los ficheros Docker, en este paso solo se llevarán los ficheros que nosotros le indiquemos, en nuestro caso será toda la aplicación.

Paso 4: declarar la red a la que pertenece el contenedor, para tener acceso entre los diferentes servicios, simplemente con la etiqueta network, y a continuación el nombre, siempre con la estructrura de fichero YAML.

El resultado final de la definición de nuestra aplicación es el siguiente:

 services:
   app:
     build:
       args:
         user: xules
         uid: 1000
       context: ./
       dockerfile: Dockerfile
     image: cx-lcustomerdb
     container_name: cx-lcustomerdb-app
     restart: unless-stopped
     working_dir: /var/www/
     volumes:
       - ./:/var/www
     networks:
       - net-cx-lcustomerdb

Servicio db: base de datos MySQL

En este servicio creamos el servidor de base de datos MySQL, para ello utilizaremos la imagen mysql:5.7, nos serviremos de las variables de entorno que hemos definido en el fichero .env al crear el proyecto con Laravel, docker-compose entiende por defecto está configuración si el fichero tuviese otro nombre se le podría indicar con la etiqueta env_file.

Vamos a ver ahora paso paso como creamos el servicio db:

Paso 1: le damos un nombre a nuestra imagen y a nuestro contenedor:

  • image: nombre de la imagen que vamos a utilizar en este caso mysql:5.7 , puedes consultar las imágenes disponibles de docker en Docker Hub MySQL Official, si por ejemplo quisiésemos utilizar la última disponible utilizariamos: mysql:latest
  • container_name: nombre del contenedor, esto nos servirá para acceder a él por el nombre, y para referenciarlo en las visualizaciones y comandos docker, en este ejemplo: cx-lcustomerdb-db
  • restart: unless-stopped, con este comando definimos que una vez lanzada nuestra aplicación está este siempre activa a no ser que se paré a propósito.

Paso 2: variables de entorno, en la etiqueta enviroment definimos los diferentes parámetros de configuración de la base de datos:

  • enviroment: como explicamos cogemos la configuración directamente de .env que se encuentra en el mismo directorio que el fichero docker-compose. Como veremos en el ejemplo definimos:
    • Nombre de la base de datos
    • Password root.
    • Usuario MySQL
    • Password Usuario MySQL
    • Además de las etiquetas de servicio como entorno (dev) y nombre (mysql)

Paso 3: definición del volumen:

  • volumes: un volumen es un espacio de almacenamiento externo al contenedor que nos permite tener persistencia de los datos con los ficheros Docker, en este paso solo se llevarán los ficheros que nosotros le indiquemos, en nuestro caso será toda la aplicación. En este caso externalizamos el contenido de la base de datos para obtener persistencia.

Paso 4: declarar la red a la que pertenece el contenedor, para tener acceso entre los diferentes servicios, simplemente con la etiqueta network, y a continuación el nombre, siempre con la estructrura de fichero YAML, y también el puerto de acceso externo.

  • ports: en la etiqueta ports establecemos la vinculación entre el puerto interno del contenedor y el acceso externo, esto nos servirá para acceder desde fuera, ya que los contenedores están conectados en la misma red y tienen acceso entre ellos. En este caso utilizamos localhost y el puerto 23306, la sintaxis es: 127.0.0.1:23306:3306
  db:
     image: mysql:5.7
     container_name: cx-lcustomerdb-db
     restart: unless-stopped
     ports:
      - 127.0.0.1:23306:3306
     environment:
       MYSQL_DATABASE: ${DB_DATABASE}
       MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
       MYSQL_PASSWORD: ${DB_PASSWORD}
       MYSQL_USER: ${DB_USERNAME}
       SERVICE_TAGS: dev
       SERVICE_NAME: mysql
     volumes:
       - ./docker-compose/mysql:/docker-entrypoint-initdb.d
       - ../cx-lcustomerdb-db/var/lib/mysql:/var/lib/mysql
     networks:
       - net-cx-lcustomerdb

Servicio nginx: servidor de aplicaciones Nginx

Para el servidor de aplicaciones utizamos Nginx como ya hemos comentado antes, hemos visto un fichero de configuración estándar de Nginx y donde el servidor estará escuchando en el puerto 80. Utilizamos la imagen nginx:alpine , alpine hace referencia a Alpine Linux Project que es una distribución de Linux optimizado, orientada a la seguridad y de muy poco peso.

Al igual que en los servicios anteriores explicamos paso a paso la configuración:

Paso 1: le damos un nombre a nuestra imagen y a nuestro contenedor:

  • image: nombre de la imagen que vamos a utilizar en este caso nginx:alpine, en el Docker Hub Nginx Official tienes un listado de las imágenes que puede utilizar así como información más detallada de todas las posibilidades de configuración.
  • container_name: nombre del contenedor, esto nos servirá para acceder a él por el nombre, y para referenciarlo en las visualizaciones y comandos docker, en este ejemplo: cx-lcustomerdb-db
  • restart: unless-stopped, con este comando definimos que una vez lanzada nuestra aplicación está este siempre activa a no ser que se paré a propósito.

Paso 2: definición del volumen y el entorno de trabajo:

  • volumes: como ya explicamos relacionamos el espacio interno del contenedor con el externo, en este caso definimos que en /var/www/ vamos a tener directamente nuestro proyecto, y indiciamos también donde definimos el fichero de configuración de Nginx.

Paso 3: declaramos la red y el puerto de acceso externo.

  • ports: definimos la relación entre puerto, en este caso utilizamos localhost y el puerto 28000 a nivel interno y como el servidor Nginx está escuchando en el puerto 80 la sintaxis sería: 127.0.0.1:28000:80

Todos estos puntos son los que puedes ver a continuación en la definición del servicio Nginx:

 nginx:
     image: nginx:alpine
     container_name: cx-lcustomerdb-nginx
     restart: unless-stopped
     ports:
       - 127.0.0.1:28000:80
     volumes:
       - ./:/var/www
       - ./docker-compose/nginx:/etc/nginx/conf.d/
     networks:
       - net-cx-lcustomerdb

Construimos nuestros contenedores e instalamos dependencias

Una vez que ya hemos completado nuestro fichero llega el momento de construirlo para ello docker-compose descargá las imágenes solicitadas sino disponemos de ellas en nuestro equipo, antes os dejo el fichero completo de docker-compose:

version: "3"
 services:
   app:
     build:
       args:
         user: xules
         uid: 1000
       context: ./
       dockerfile: Dockerfile
     image: cx-lcustomerdb
     container_name: cx-lcustomerdb-app
     restart: unless-stopped
     working_dir: /var/www/
     volumes:
       - ./:/var/www
     networks:
       - net-cx-lcustomerdb
   db:
     image: mysql:5.7
     container_name: cx-lcustomerdb-db
     restart: unless-stopped
     ports:
      - 127.0.0.1:23306:3306
     environment:
       MYSQL_DATABASE: ${DB_DATABASE}
       MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
       MYSQL_PASSWORD: ${DB_PASSWORD}
       MYSQL_USER: ${DB_USERNAME}
       SERVICE_TAGS: dev
       SERVICE_NAME: mysql
     volumes:
       - ./docker-compose/mysql:/docker-entrypoint-initdb.d
       - ../cx-lcustomerdb-db/var/lib/mysql:/var/lib/mysql
     networks:
       - net-cx-lcustomerdb
 nginx:
     image: nginx:alpine
     container_name: cx-lcustomerdb-nginx
     restart: unless-stopped
     ports:
       - 127.0.0.1:28000:80
     volumes:
       - ./:/var/www
       - ./docker-compose/nginx:/etc/nginx/conf.d/
     networks:
       - net-cx-lcustomerdb
 networks:
   net-cx-lcustomerdb:
     driver: bridge

Para lanzar la construcción de las indicaciones del fichero utilizamos uno de los comandos que nos proporciona docker-compose que es build:

xules@xxxxx:~/$ docker-compose build

Al ejecutar el comando se construye la imagen que hemos definido en nuestro fichero Dockerfile, y también se descargan y construyen las imágenes de los otros servicios, una vez finalizado ya podemos lanzar nuestros contenedores con el comando up, cuando los queramos parar utilizaremos down:

xules@xxxxx:~/$ docker-compose up

Una vez tenemos ya levantada la aplicación, procedemos a instalar composer y node, para instalar composer lanzamos composer install dentro del contenedor:

xules@xxxxx:~/$ docker-compose exec app composer install
Installing dependencies from lock file (including require-dev) Verifying lock file contents can be installed on current platform. Nothing to install, update or remove Generating optimized autoload files 
Illuminate\Foundation\ComposerScripts::postAutoloadDump @php artisan package:discover --ansi
Discovered Package: facade/ignition &amp;amp;amp;amp;amp;amp;amp;amp;lt;/code&amp;amp;amp;amp;amp;amp;amp;amp;gt;
Discovered Package: fideloper/proxy&amp;amp;amp;amp;amp;amp;amp;amp;lt;/code&amp;amp;amp;amp;amp;amp;amp;amp;gt;
Discovered Package: fruitcake/laravel-cors Discovered Package: laravel/sail
Discovered Package: laravel/tinker 
Discovered Package: nesbot/carbon 
Discovered Package: nunomaduro/collision Package manifest generated successfully.
74 packages you are using are looking for funding. 
Use the `composer fund` command to find out more!

Seguimos ahora con la generación de la clave para nuestra aplicación con la llamada a artisan, para ello con docker-compose llamamos a la aplicación app para que ejecute la generación automática de la clave:

xules@xxxxx:~/$ docker-compose exec app php artisan key:generate
 Application key set successfully.

Instalamos node y npm con los paquetes que hemos decargado y copiado de la imagen original:

xules@xxxxx:~/$ docker-compose exec app npm install
 npm WARN deprecated uuid@3.4.0: Please upgrade  to version 7 or higher.  Older versions may use Math.random() in certain circumstances, which is known to be problematic.  See https://v8.dev/blog/math-random for details.
 npm WARN deprecated querystring@0.2.0: The querystring API is considered Legacy. new code should use the URLSearchParams API instead.
 added 768 packages, and audited 769 packages in 2m
 80 packages are looking for funding
   run npm fund for details

Una vez finalizado este proceso ya tenemos listo nuestro entorno de trabajo para empezar a desarrollar con Laravel en nuestro entorno con contenedores, una vez lanzada la aplicación accedemos por la dirección externa que hemos definido en el servicio nginx, este es el resultado:

Docker Laravel Home page
Docker Laravel Home page

5. Documentación y referencias