All articles
  • Symfony
  • Docker
  • DevOps

A clean Docker setup for a Symfony project

PHP-FPM, Nginx and MySQL with Docker Compose — a simple, reproducible foundation for building a Symfony application as a team.

"It works on my machine": with Docker, that sentence has no reason to exist. The whole team shares the same environment, from the PHP version to the installed extensions. Here is the foundation I use to start a Symfony project: three services, a short Dockerfile and a few commands.

The architecture

  • php: PHP-FPM with the extensions Symfony needs;
  • nginx: the web server, forwarding PHP requests to FPM;
  • database: MySQL 8.

The PHP Dockerfile

Dockerfile
FROM php:8.3-fpm

RUN apt-get update && apt-get install -y git unzip libicu-dev libzip-dev \
    && docker-php-ext-install intl pdo_mysql opcache zip \
    && rm -rf /var/lib/apt/lists/*

COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
WORKDIR /var/www/app

Copying Composer from its official image avoids a manual install and guarantees a recent version.

The compose.yaml file

YAML
services:
  php:
    build: .
    volumes:
      - ./:/var/www/app
    depends_on: [database]

  nginx:
    image: nginx:1.27-alpine
    ports: ["8080:80"]
    volumes:
      - ./:/var/www/app
      - ./docker/nginx.conf:/etc/nginx/conf.d/default.conf:ro
    depends_on: [php]

  database:
    image: mysql:8.0
    environment:
      MYSQL_DATABASE: app
      MYSQL_USER: app
      MYSQL_PASSWORD: app
      MYSQL_ROOT_PASSWORD: root
    volumes:
      - db-data:/var/lib/mysql

volumes:
  db-data:

The named volume db-data keeps the database between restarts.

The Nginx configuration

Nginx
server {
    listen 80;
    root /var/www/app/public;

    location / {
        try_files $uri /index.php$is_args$args;
    }

    location ~ ^/index\.php(/|$) {
        fastcgi_pass php:9000;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        internal;
    }
}

fastcgi_pass php:9000 works because Docker Compose creates a network where every service is reachable by its name.

Wiring Symfony

In .env.local, the database host is simply the service name:

Terminal
DATABASE_URL="mysql://app:app@database:3306/app?serverVersion=8.0"

Then start everything:

Terminal
docker compose up -d --build
docker compose exec php composer install
docker compose exec php bin/console doctrine:migrations:migrate

The application answers on http://localhost:8080.

My tips

  • A Makefile with shortcuts (make up, make sh, make migrate) saves the whole team time.
  • Xdebug on demand: install it in a separate Dockerfile stage or enable it through an environment variable, so it doesn't slow down everyday work.
  • Never commit secrets: keep credentials in .env.local or in Symfony's secrets vault.
  • To go further, Kévin Dunglas's open-source symfony-docker project offers a very complete base with FrankenPHP and local HTTPS.

This setup is deliberately minimal: it reads in two minutes, and anyone can extend it to fit the project.