3-Instalación de Docker, Laravel con Nginx y MySQL, y control de versiones con Git
Instalación de Docker, configuración de Laravel con Nginx y MySQL, y control de versiones con Git
Este tutorial te guiará paso a paso desde la instalación de Docker y Docker Compose (como plugin) en Linux (Debian/Ubuntu) hasta la configuración completa de un entorno de desarrollo para Laravel con tres servicios: PHP‑FPM, Nginx y MySQL. Además, entenderás el funcionamiento de los volúmenes (bind mounts) que sincronizan automáticamente los archivos entre tu máquina host y los contenedores.
A lo largo del proceso incorporaremos Git para llevar un control de versiones de tu proyecto, algo esencial en cualquier flujo de trabajo profesional.
Tabla de contenido
¿Qué son los volúmenes en Docker y por qué son mágicos?
Instalación de Docker y Docker Compose (método oficial)
Estructura de archivos inicial
Creación del Dockerfile personalizado
Creación del archivo docker-compose.yml (servicio app)
Construir y levantar el contenedor de la aplicación
Creación del proyecto Laravel dentro del contenedor
Verificar la sincronización en tiempo real
Uso de Git para control de versiones
Configurar Nginx y MySQL
Solución de problemas comunes
Conclusión
Comandos útiles de Docker Compose (nueva sintaxis)
¿Qué son los volúmenes en Docker y por qué son mágicos?
Cuando trabajamos con contenedores, todo lo que sucede dentro de ellos está aislado del sistema anfitrión. Sin embargo, para desarrollar aplicaciones necesitamos que los cambios en el código se reflejen instantáneamente y que los archivos generados (como los de Composer) estén disponibles en nuestro editor local.
Aquí es donde entran los bind mounts:
Un bind mount monta un directorio del host dentro del contenedor.
La sintaxis en
docker-compose.ymles:volumes: - ./ruta/local:/ruta/en/contenedorCualquier archivo creado, modificado o eliminado en el contenedor dentro de esa ruta, aparece automáticamente en tu host y viceversa.
Esto permite editar el código con tu IDE favorito mientras el contenedor ejecuta la aplicación, sin necesidad de reconstruir la imagen.
En este tutorial usaremos el siguiente montaje:
volumes:
- ./:/var/wwwEs decir, el directorio actual (donde tienes tu proyecto) se monta en /var/www dentro del contenedor. Todos los archivos de Laravel vivirán ahí.
Instalación de Docker y Docker Compose (método oficial)
Sigue estos pasos para instalar Docker y Docker Compose (como plugin) usando el repositorio oficial. Este es el método recomendado actualmente.
Novedad: Hoy en día Docker Compose se instala como un plugin y se utiliza con el comando docker compose (con espacio, sin guion). El antiguo docker-compose (con guion) está en desuso. A lo largo de este tutorial usaremos la sintaxis moderna.
1. Actualizar paquetes e instalar dependencias
sudo apt update
sudo apt install ca-certificates curl -y2. Agregar la clave GPG oficial de Docker
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc3. Agregar el repositorio oficial de Docker a las fuentes de APT
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null4. Actualizar el índice de paquetes e instalar Docker
sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin -ydocker-ce: El motor de Docker (Community Edition).docker-compose-plugin: El plugin de Docker Compose (ya no necesitas instalardocker-composepor separado).
5. Verificar la instalación
Para confirmar que todo ha ido bien, ejecuta el contenedor de prueba:
sudo docker run hello-worldDeberías ver el mensaje de bienvenida de Docker.
6. Agregar tu usuario al grupo docker (para evitar usar sudo)
sudo groupadd docker # si no existe
sudo usermod -aG docker $USERPara aplicar los cambios sin cerrar sesión:
newgrp dockerPrueba que todo funciona sin sudo:
docker run hello-world7. Verificar la versión de Docker Compose
Con el nuevo plugin, el comando es docker compose (con espacio). Verifica que está instalado:
docker compose versionDeberías ver algo como Docker Compose version v2.xx.x. Si ves esto, estás listo para usar la sintaxis moderna.
Estructura de archivos inicial
Antes de empezar, asegúrate de tener una carpeta vacía para tu proyecto. Nosotros la llamaremos laravel-docker (puedes elegir cualquier nombre).
Crear la carpeta:
mkdir laravel-docker
cd laravel-dockerÁrbol inicial en el host:
~/laravel-docker/
├── Dockerfile (lo crearemos)
└── docker-compose.yml (lo crearemos)Creación del Dockerfile personalizado
Vamos a crear un Dockerfile que defina la imagen de nuestro contenedor principal (PHP‑FPM). Este contenedor ejecutará el código de Laravel.
Crear el archivo Dockerfile:
nano DockerfilePega el siguiente contenido (explicado línea por línea):
# 1. Imagen base: PHP 8.2 con FPM (recomendado para Laravel 10/11)
FROM php:8.2-fpm
# 2. Argumentos para el usuario (se pasan al construir la imagen)
ARG USER=laravel
ARG UID=1000
# 3. Instalar dependencias del sistema y extensiones de PHP
RUN apt-get update && apt-get install -y \
git \
curl \
libpng-dev \
libonig-dev \
libxml2-dev \
zip \
unzip \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# 4. Instalar extensiones de PHP necesarias para Laravel
RUN docker-php-ext-install \
pdo_mysql \
mbstring \
exif \
pcntl \
bcmath \
gd
# 5. Instalar Composer (globalmente)
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
# 6. Crear un usuario no root (para seguridad)
RUN useradd -G www-data,root -u ${UID} -d /home/${USER} ${USER} \
&& mkdir -p /home/${USER}/.composer \
&& chown -R ${USER}:${USER} /home/${USER}
# 7. Establecer el directorio de trabajo
WORKDIR /var/www
# 8. Cambiar al usuario creado (a partir de aquí se ejecuta con ese usuario)
USER ${USER}
# 9. Comando por defecto (opcional, se puede sobrescribir en docker-compose)
CMD ["php-fpm"]Explicación del Dockerfile:
FROM php:8.2-fpm: imagen base ligera con PHP 8.2 y FPM.ARG USERyARG UID: permiten pasar el nombre y UID del usuario del host desde docker-compose, para que los archivos creados tengan los mismos propietarios.Instalamos paquetes esenciales y extensiones de PHP (
pdo_mysql,mbstring,gd, etc.) necesarias para Laravel.Instalamos Composer globalmente.
Creamos un usuario no root con el UID proporcionado, lo agregamos a los grupos
www-datayroot.El directorio de trabajo es
/var/www.Cambiamos al usuario creado para que los comandos se ejecuten con sus permisos.
Creación del archivo docker-compose.yml (servicio app)
Este archivo orquesta nuestros servicios. Definiremos el servicio app (nuestro contenedor PHP‑FPM). Más adelante añadiremos MySQL y Nginx.
Crear docker-compose.yml:
nano docker-compose.ymlPega el siguiente contenido:
version: '3'
services:
app:
build:
context: .
dockerfile: Dockerfile
args:
user: ${USER:-udemy} # Usa el usuario del sistema o 'udemy' por defecto
uid: ${UID:-1000} # Usa el UID del sistema o 1000 por defecto
image: laravel-app
container_name: laravel-app-1
restart: unless-stopped
working_dir: /var/www
volumes:
- ./:/var/www # <-- El bind mount mágico
networks:
- laravel
networks:
laravel:
driver: bridgePuntos clave:
build: construye la imagen a partir delDockerfiledel directorio actual, pasando los argumentosuseryuid. Para obtener el UID actual de tu usuario, ejecutaid -uy asígnalo a una variable de entorno o simplemente usa1000.volumes: - ./:/var/www: bind mount que monta el directorio local dentro del contenedor en/var/www. Este es el corazón de la sincronización.networks: crea una red internalaravelpara que los servicios se comuniquen entre sí.
Construir y levantar el contenedor de la aplicación
Ejecutar docker compose up -d (nota: usamos el nuevo comando sin guion):
docker compose up -dEste comando:
Construye la imagen según el
Dockerfile(descarga la basephp:8.2-fpmy ejecuta los pasos).Crea el contenedor
laravel-app-1y lo inicia en segundo plano (-d).Monta el volumen
./en/var/www.
Posible problema: La descarga de la imagen base a veces se queda "congelada". Si eso ocurre, presiona Ctrl+C y vuelve a ejecutar el comando. Como ya se habrán descargado algunas capas, el proceso continuará más rápido.
Verificar que el contenedor está corriendo:
docker psDeberías ver algo como:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
xxxxxxxxxxxx laravel-app "docker-php-entrypoi…" ... seconds Up ... laravel-app-1Comandos útiles:
Detener el contenedor:
docker stop <nombre_o_id>Borrar el contenedor:
docker rm <nombre_o_id>Forzar el borrado de uno activo:
docker rm -f <nombre_o_id>Ver contenedores:
docker psConstruir contenedor:
docker compose up -dIngresar al contenedor:
docker exec -it laravel-app-1 /bin/bashSalir del contenedor:
exit
Creación del proyecto Laravel dentro del contenedor
Ahora usaremos Composer (instalado en el contenedor) para crear un nuevo proyecto Laravel dentro de /var/www. Gracias al bind mount, los archivos que genere Composer aparecerán instantáneamente en tu host.
Acceder al contenedor:
docker exec -it laravel-app-1 /bin/bashAhora estás dentro del contenedor como el usuario que definiste. El prompt debería mostrar algo como udemy@<container_id>:/var/www$.
Crear el proyecto Laravel:
composer create-project laravel/laravel application --prefer-distComposer descargará Laravel y creará una carpeta application dentro de /var/www con todos los archivos del framework.
¡Milagro de los volúmenes! En el mismo instante, abre tu explorador de archivos en el host (~/laravel-docker/) y verás que aparece una carpeta application con toda la estructura de Laravel. Los archivos se han sincronizado automáticamente desde el contenedor al host.
Estructura en el host (después de composer create-project):
~/laravel-docker/
├── application/
│ ├── app/
│ ├── bootstrap/
│ ├── config/
│ ├── database/
│ ├── public/
│ ├── resources/
│ ├── routes/
│ ├── storage/
│ ├── tests/
│ ├── vendor/
│ ├── .env
│ ├── composer.json
│ └── ... (otros archivos)
├── Dockerfile
└── docker-compose.ymlEstructura en el contenedor: exactamente la misma, porque /var/www está montado sobre el directorio del host. Ambos comparten el mismo árbol de archivos.
Copiar los archivos al directorio raíz y eliminar la subcarpeta application
Para que el código quede directamente en /var/www y no en una subcarpeta, podemos mover todos los archivos de application/ a la raíz del proyecto.
Desde el host (fuera del contenedor):
cp -RT application/ .
rm -rf applicationO desde dentro del contenedor:
cp -RT application/ . && rm -rf applicationVerifica que la estructura ahora sea:
~/laravel-docker/
├── app/
├── bootstrap/
├── config/
├── database/
├── public/
├── resources/
├── routes/
├── storage/
├── tests/
├── vendor/
├── .env
├── composer.json
├── Dockerfile
└── docker-compose.ymlGenerar la clave de la aplicación (APP_KEY)
Dentro del contenedor (o desde el host, pero es mejor dentro para asegurar permisos), ejecuta:
php artisan key:generateEsto modificará el archivo .env que está sincronizado, así que verás el cambio en el host.
Nota: Lo más recomendable es ejecutar
php artisan key:generatedentro del contenedor, no en el host.
Verificar la sincronización en tiempo real
Para confirmar que el bind mount funciona, realiza una prueba:
Desde el host, crea un archivo
test.txtdentro de~/laravel-docker/.Dentro del contenedor, ejecuta
ls /var/wwwy verástest.txt.Desde el contenedor, crea otro archivo
touch /var/www/from-container.txt.En el host, verás que aparece
from-container.txtautomáticamente.
Esto demuestra que el directorio es compartido en ambos sentidos.
Uso de Git para control de versiones
Ahora que tienes el proyecto Laravel funcionando, es el momento de inicializar un repositorio Git y empezar a versionar tu código. El control de versiones te permitirá registrar cambios, colaborar con otros y mantener un historial limpio de tu desarrollo.
Importante: Todos los comandos de Git se ejecutan desde el host (tu máquina local), ya que los archivos están sincronizados y puedes usar tu editor o IDE favorito. No es necesario instalar Git dentro del contenedor (aunque lo incluimos en el Dockerfile por si acaso).
1. Inicializar el repositorio
Desde la carpeta raíz del proyecto (~/laravel-docker/), ejecuta:
git init2. Crear un archivo .gitignore apropiado para Laravel
Dentro de la misma carpeta, crea un archivo .gitignore con el siguiente contenido (puedes generarlo automáticamente con herramientas como laravel o gitignore.io). Pega esto:
/vendor
/node_modules
.env
.env.backup
.env.production
*.log
*.sqlite
*.sqlite-journal
/storage/*.key
/storage/framework/cache/
/storage/framework/sessions/
/storage/framework/views/
/public/hot
/public/storage
Homestead.json
Homestead.yaml
/.vagrant
/.phpunit.result.cache
/.idea
/.vscode
.DS_Store
Thumbs.dbExplicación: Ignoramos
vendor/(dependencias de Composer),.env(variables de entorno), los logs y archivos de caché. También ignoramos archivos propios de editores y sistemas operativos.
3. Añadir todos los archivos al área de staging y hacer el primer commit
git add .
git commit -m "Primer commit: proyecto Laravel con Docker"Con esto, tu proyecto ya está bajo control de versiones. Ahora, cada vez que realices cambios significativos en el código (por ejemplo, añadir una nueva ruta, controlador o vista), puedes hacer commits desde el host de la forma habitual:
git add .
git commit -m "Descripción del cambio"4. Integrar Git con el flujo de trabajo con contenedores
Cambios en el código: Como los volúmenes sincronizan los archivos, cualquier modificación que hagas en el host se refleja al instante dentro del contenedor. No necesitas reconstruir ni reiniciar el contenedor para ver los cambios (excepto si cambias configuraciones de Nginx o PHP, que sí requieren reinicio).
Cambios en dependencias: Si añades un nuevo paquete con Composer (por ejemplo,
composer require spatie/laravel-permission), hazlo dentro del contenedor (o desde el host ejecutandodocker exec -it laravel-app-1 composer require ...). Los archivoscomposer.jsonycomposer.lockse actualizarán en el host, y deberás commitearlos.Migraciones y cambios en la base de datos: Ejecuta
php artisan migratedentro del contenedor. Los archivos de migración (endatabase/migrations) estarán en el host y podrás commitearlos.Reconstruir la imagen: Si modificas el
Dockerfile(por ejemplo, para añadir una nueva extensión de PHP), deberás reconstruir la imagen condocker compose up -d --buildy luego reiniciar los servicios.
5. Buenas prácticas con Git y Docker
Mantén el
.envfuera del repositorio (ya lo ignoramos). Usa un archivo.env.examplepara documentar las variables necesarias.Commitear los archivos de configuración como
docker-compose.ymlyDockerfile, ya que definen el entorno de desarrollo y son parte del proyecto.Usa ramas para desarrollar nuevas funcionalidades y fusionarlas a
maincuando estén listas.Aprovecha los hooks de Git (por ejemplo,
pre-commit) para ejecutar tests o linters dentro del contenedor si lo deseas.
6. Conexión con un repositorio remoto (opcional)
Si quieres alojar tu código en GitHub, GitLab u otro servicio, sigue estos pasos:
Crea un repositorio vacío en la plataforma elegida.
Enlaza tu repositorio local con el remoto:
git remote add origin https://github.com/tu-usuario/tu-repositorio.gitSube tu código:
git push -u origin main
Recuerda que el repositorio remoto no debe contener archivos sensibles como
.env. Asegúrate de que tu.gitignoreesté bien configurado.
Configurar Nginx y MySQL
Ahora vamos a agregar los servicios de Nginx (servidor web) y MySQL (base de datos) para tener un entorno completo.
Paso 1: Detener contenedores y crear estructura para Nginx
Primero, asegurémonos de que ningún contenedor esté corriendo:
docker compose downLuego, crea la estructura de carpetas para la configuración de Nginx:
mkdir -p docker-compose/nginxPaso 2: Crear archivo de configuración de Nginx
Dentro de docker-compose/nginx, crea un archivo llamado app.laravel.conf:
nano docker-compose/nginx/app.laravel.confPega el siguiente contenido (boilerplate oficial de Laravel para Nginx):
server {
listen 80;
server_name localhost;
root /var/www/public;
add_header X-Frame-Options "SAMEORIGIN";
add_header X-Content-Type-Options "nosniff";
index index.php;
charset utf-8;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
error_page 404 /index.php;
location ~ \.php$ {
fastcgi_pass app:9000;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
}
location ~ /\.(?!well-known).* {
deny all;
}
}Partes clave:
root /var/www/public;→ ruta donde Nginx buscará elindex.php.listen 80;→ puerto en el que escuchará dentro del contenedor.fastcgi_pass app:9000;→ el nombre del contenedor de PHP‑FPM (en nuestro casoapp) y el puerto 9000 (el que expone PHP‑FPM).
Guarda el archivo (Ctrl+O, Enter) y sal (Ctrl+X).
Paso 3: Modificar docker-compose.yml (agregar Nginx y MySQL)
Ahora editaremos docker-compose.yml para agregar los dos nuevos servicios. Abre el archivo:
nano docker-compose.ymlAgregar el servicio Nginx (debajo del servicio app):
nginx:
image: nginx:alpine
restart: unless-stopped
ports:
- "8000:80"
volumes:
- ./:/var/www
- ./docker-compose/nginx:/etc/nginx/conf.d
networks:
- laravelAgregar el servicio MySQL (db):
db:
image: mysql:8
restart: unless-stopped
environment:
MYSQL_DATABASE: ${DB_DATABASE}
MYSQL_USER: ${DB_USERNAME}
MYSQL_PASSWORD: ${DB_PASSWORD}
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
networks:
- laravelEl archivo completo debería verse así:
version: '3'
services:
app:
build:
context: .
dockerfile: Dockerfile
args:
user: ${USER:-udemy}
uid: ${UID:-1000}
image: laravel-app
container_name: laravel-app-1
restart: unless-stopped
working_dir: /var/www
volumes:
- ./:/var/www
networks:
- laravel
nginx:
image: nginx:alpine
restart: unless-stopped
ports:
- "8000:80"
volumes:
- ./:/var/www
- ./docker-compose/nginx:/etc/nginx/conf.d
networks:
- laravel
db:
image: mysql:8
restart: unless-stopped
environment:
MYSQL_DATABASE: ${DB_DATABASE}
MYSQL_USER: ${DB_USERNAME}
MYSQL_PASSWORD: ${DB_PASSWORD}
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
networks:
- laravel
networks:
laravel:
driver: bridgeGuarda los cambios (Ctrl+O, Enter) y sal (Ctrl+X).
Paso 4: Ajustar el archivo .env de Laravel
El archivo .env en la raíz de tu proyecto contiene las variables de conexión a la base de datos. Debemos editarlo para que los valores coincidan con los que usaremos en el contenedor db.
Abre el .env:
nano .envBusca o agrega las siguientes líneas (ajusta los valores como desees):
DB_CONNECTION=mysql
DB_HOST=db
DB_PORT=3306
DB_DATABASE=laravel_db
DB_USERNAME=laravel_user
DB_PASSWORD=secretPuntos importantes:
DB_HOSTdebe serdb(el nombre del servicio en Docker Compose). No useslocalhostni127.0.0.1, porque dentro del contenedor de la aplicación,localhostsería él mismo, no la base de datos.DB_DATABASE,DB_USERNAMEyDB_PASSWORDdeben coincidir con lo que definiste en elenvironmentdel serviciodb.El puerto 3306 es el predeterminado de MySQL.
Guarda el archivo.
Paso 5: Levantar los contenedores con la nueva configuración
Ahora que todo está listo, levanta los servicios con Docker Compose:
docker compose up -dEste comando construirá la imagen de la aplicación (si no está construida), descargará las imágenes de Nginx y MySQL (si no las tienes), y creará los tres contenedores.
Verifica que los tres contenedores estén en ejecución:
docker psDeberías ver app, nginx y db en la lista.
Paso 6: Verificar que la aplicación funciona
Abre tu navegador y visita http://localhost:8000. Deberías ver la página de bienvenida de Laravel.
Si ves algún error, revisa los logs con:
docker compose logs nginx
docker compose logs app
docker compose logs dbSolución de problemas comunes
La descarga de imágenes se queda pegada
Si al levantar los contenedores ves que la descarga de una imagen se detiene sin avanzar, presiona Ctrl + C para cancelar y vuelve a ejecutar docker compose up -d. Como muchas capas ya están descargadas, el proceso continuará más rápido.
Error de conexión a la base de datos
Verifica que
DB_HOSTen.envseadby nolocalhost.Comprueba que los nombres de usuario y contraseña coincidan con los definidos en
environmentdel serviciodb.Asegúrate de que el servicio
dbesté corriendo:docker ps | grep db.
Error de permiso en los volúmenes
Si Nginx no puede leer los archivos, ajusta los permisos en el host o usa user: www-data en el servicio app.
El puerto 8000 ya está ocupado
Cambia el puerto en ports (por ejemplo, "8001:80") y accede por ese nuevo puerto.
La configuración de Nginx no se carga
Asegúrate de que el archivo de configuración termine en .conf y esté en la ruta correcta del volumen (./docker-compose/nginx). Dentro del contenedor, Nginx lee todos los archivos .conf en /etc/nginx/conf.d.
Composer no se encuentra dentro del contenedor
Verifica que en tu Dockerfile hayas instalado Composer correctamente (con COPY --from=composer:latest o con el instalador).
El contenedor no se crea correctamente
Revisa los logs con docker compose logs para identificar errores específicos.
Conclusión
Has construido un entorno de desarrollo completo para Laravel con Docker, donde:
Los archivos se sincronizan automáticamente gracias a los bind mounts.
Los servicios PHP‑FPM, Nginx y MySQL están orquestados con Docker Compose.
El proyecto está bajo control de versiones con Git, lo que te permite gestionar cambios, colaborar y mantener un historial limpio.
Este flujo de trabajo es escalable y fácil de adaptar a otros entornos (producción, pruebas, etc.) simplemente cambiando variables de entorno o configuraciones.
Comandos útiles de Docker Compose (nueva sintaxis)
| Comando | Descripción |
|---|---|
docker compose up -d | Levanta los servicios en segundo plano. |
docker compose down | Detiene y elimina los contenedores (redes y volúmenes por defecto se conservan). |
docker compose down -v | Detiene y elimina también los volúmenes nombrados. |
docker compose ps | Lista los contenedores del proyecto. |
docker compose logs | Muestra los logs de todos los servicios. |
docker compose logs <servicio> | Muestra logs de un servicio específico (ej. nginx). |
docker compose exec <servicio> <comando> | Ejecuta un comando dentro de un contenedor en ejecución (ej. docker compose exec app bash). |
docker compose build | Reconstruye las imágenes sin levantar los contenedores. |
docker compose up -d --build | Reconstruye y levanta los contenedores. |
¡Ya tienes todo lo necesario para empezar a desarrollar con Laravel, Docker y Git!
Comentarios
Publicar un comentario