LANTORIANDocker, pas à pas

Chapitre 740 min1 exercice

Laravel sous Docker

Tu as toutes les briques. Assemble-les pour une API Laravel : un serveur web, PHP, une base de données et un cache, chacun dans son conteneur.

Le trajet d'une requête

PHP-FPM exécute le PHP mais ne sait pas parler HTTP. Nginx reçoit les requêtes et lui transmet celles qui concernent Laravel. D'où deux conteneurs pour l'application.

Quatre conteneurs, un rôle chacun. Suis une requête GET /api/users de bout en bout.

Les fichiers

Le code Laravel vit dans api/, la configuration Docker dans docker/. Cette organisation te resservira au chapitre 9, quand le front Next.js arrivera à côté.

Arborescence
projet/
├── compose.yaml
├── docker/
│   ├── nginx/default.conf
│   └── php/Dockerfile
└── api/                 ← le projet Laravel
docker/php/Dockerfile
FROM php:8.4-fpm

# Installe les extensions PHP dont Laravel a besoin
COPY --from=mlocati/php-extension-installer /usr/bin/install-php-extensions /usr/local/bin/
RUN install-php-extensions pdo_pgsql redis intl zip bcmath opcache

# Composer, récupéré depuis son image officielle
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
ENV COMPOSER_HOME=/tmp/composer

# Aligne www-data sur ton utilisateur : fini les erreurs de permission sur storage/
ARG UID=1000
ARG GID=1000
RUN groupmod -o -g ${GID} www-data && usermod -o -u ${UID} -g www-data www-data

WORKDIR /var/www/html
USER www-data
docker/nginx/default.conf
server {
    listen 80;
    root /var/www/html/public;
    index index.php;

    client_max_body_size 20M;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        fastcgi_pass php:9000;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;
    }
}

root pointe vers public/, et fastcgi_pass php:9000 désigne le service php par son nom. Tu reconnais le réseau du chapitre 5.

compose.yaml
services:
  nginx:
    image: nginx:stable-alpine
    ports:
      - "8000:80"
    volumes:
      - ./api:/var/www/html
      - ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
    depends_on:
      - php

  php:
    build:
      context: ./docker/php
      args:
        UID: ${UID:-1000}
        GID: ${GID:-1000}
    volumes:
      - ./api:/var/www/html
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started

  db:
    image: postgres:18
    environment:
      POSTGRES_DB: laravel
      POSTGRES_USER: laravel
      POSTGRES_PASSWORD: ${DB_PASSWORD:-secret}
    volumes:
      - db-data:/var/lib/postgresql
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U laravel -d laravel"]
      interval: 5s
      timeout: 3s
      retries: 10

  redis:
    image: redis:8-alpine

volumes:
  db-data:

Premier lancement

Nouveau projet

Pas besoin de PHP sur ta machine : Composer tourne dans un conteneur jetable.

Terminal
mkdir projet && cd projet
docker run --rm --user "$(id -u):$(id -g)" -e COMPOSER_HOME=/tmp/composer \
  -v "$PWD":/app -w /app composer:2 create-project laravel/laravel api

Crée les fichiers de la section précédente, puis remplace les lignes de base de données dans api/.env :

api/.env
DB_CONNECTION=pgsql
DB_HOST=db
DB_PORT=5432
DB_DATABASE=laravel
DB_USERNAME=laravel
DB_PASSWORD=secret

REDIS_HOST=redis
Terminal
docker compose up -d --build
docker compose exec php php artisan migrate

Ouvre localhost:8000 : la page d'accueil de Laravel s'affiche.

Projet existant de l'équipe

Terminal
git clone <url-du-depot> projet && cd projet
cp api/.env.example api/.env
docker compose up -d --build
docker compose exec php composer install
docker compose exec php php artisan key:generate
docker compose exec php php artisan migrate --seed

Au quotidien

Toutes les commandes PHP passent par docker compose exec php. Ajoute ces alias à ton ~/.zshrc ou ~/.bashrc pour taper moins.

~/.zshrc
alias dc="docker compose"
alias art="docker compose exec php php artisan"
alias dcomposer="docker compose exec php composer"
Terminal
art make:model Invoice -mc     # modèle, migration, contrôleur
art migrate
art tinker
art test
dcomposer require laravel/sanctum
dc logs -f php                 # erreurs PHP-FPM
dc exec php tail -f storage/logs/laravel.log

Et Vite ?

Si le projet utilise des vues Blade avec Vite, ajoute un service Node dans compose.yaml. Pour une API pure consommée par Next.js, tu n'en as pas besoin.

compose.yaml (service en plus)
  vite:
    image: node:24-slim
    user: "${UID:-1000}:${GID:-1000}"
    working_dir: /var/www/html
    environment:
      npm_config_cache: /tmp/npm
    command: sh -c "npm install && npm run dev -- --host"
    ports:
      - "5173:5173"
    volumes:
      - ./api:/var/www/html
api/vite.config.js (extrait)
export default defineConfig({
  server: {
    host: true,
    hmr: { host: "localhost" },
  },
  // plugins: [laravel(...)]
});
Exercice

Une API Laravel qui parle à PostgreSQL

  1. Crée un nouveau projet Laravel dans projet/api avec le conteneur Composer.
  2. Ajoute compose.yaml, le Dockerfile PHP et la configuration nginx de ce chapitre.
  3. Configure api/.env pour PostgreSQL et Redis, démarre la stack et lance les migrations.
  4. Ajoute une route /ping qui renvoie la version de PHP, le nom de la base et la réponse de Redis.
Ce que tu dois obtenir

curl localhost:8000/ping renvoie quelque chose comme {"php":"8.4.12","db":"laravel","redis":"ok"}. Si la base ou Redis ne répondent pas, Laravel affiche une erreur explicite.

Un indice

Dans routes/web.php, une closure suffit. DB::selectOne("select current_database() as name") interroge la base, Redis::ping() interroge Redis.

Erreur Permission denied sur storage/ ? Vérifie ton id -u et reconstruis l'image php.

Voir la solution
api/routes/web.php
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Redis;

Route::get('/ping', fn () => [
    'php' => PHP_VERSION,
    'db' => DB::selectOne('select current_database() as name')->name,
    'redis' => Redis::ping() ? 'ok' : 'ko',
]);
Terminal
docker compose up -d --build
docker compose exec php php artisan migrate
curl localhost:8000/ping
Échap