Deployer PrestaShop avec Docker Compose

Credit : Logo officiel

Deployer PrestaShop avec Docker Compose

Dylan D. — Agent Support Technique Serveur PrestaShop 1905 mots 10 min de lecture

Pourquoi je deploie tous mes nouveaux PrestaShop avec Docker Compose

J'ai bascule a Docker en 2022 sur un PrestaShop client qui tournait depuis 2019 sur un serveur Debian 9 completement pourri. Le client voulait refaire son infra. Plutot que de reinstaller a la main avec les memes risques de derive, j'ai propose Docker Compose. Resultat : 25 minutes pour remonter une stack complete, reproductible, versionnable. Depuis, c'est ma methode par defaut pour les nouveaux deploiements et les migrations.

Docker apporte trois choses concretes en e-commerce : isolation (PHP, MariaDB, Nginx), reversibilite (un docker compose down && docker compose up -d et tout est propre), et homogeneite entre dev/staging/prod. Ce guide montre exactement la stack que j'utilise sur PrestaShop 8.1, avec MariaDB 10.11, Nginx Alpine et SSL Let's Encrypt.

Prerequis et installation Docker

Sur Debian 12 ou Ubuntu 22.04 LTS :

# Repos officiels Docker
curl -fsSL https://download.docker.com/linux/debian/gpg \
  | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo "deb [signed-by=/etc/apt/keyrings/docker.gpg] \
  https://download.docker.com/linux/debian $(lsb_release -cs) stable" \
  | sudo tee /etc/apt/sources.list.d/docker.list

sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io \
  docker-buildx-plugin docker-compose-plugin

sudo usermod -aG docker $USER
newgrp docker

docker --version          # Docker version 27.x
docker compose version    # Docker Compose v2.27.x

Une machine VPS 4 CPU / 8 Go RAM tient une boutique PrestaShop avec 5000 produits et 1500 visites/jour sans broncher.

Structure du projet

Je reproduis la meme arbo sur tous les deploiements pour que tout soit pareil :

/opt/prestashop-docker/
  docker-compose.yml
  .env
  custom-php.ini
  nginx/
    default.conf
    ssl/
      fullchain.pem
      privkey.pem
  data/                # volumes nommes Docker
  backups/             # dumps automatiques
  scripts/
    backup.sh

Le fichier .env : centraliser les secrets

# Base de donnees
MYSQL_ROOT_PASSWORD=RootP@ssword2026!
MYSQL_DATABASE=prestashop
MYSQL_USER=ps_user
MYSQL_PASSWORD=PsP@ssword2026Strong!

# PrestaShop
PS_DOMAIN=shop.monsite.fr
PS_ADMIN_EMAIL=admin@monsite.fr
PS_ADMIN_PASSWORD=AdminP@ss2026Long!
PS_FOLDER_ADMIN=admin-9k3xp

# Performance
PHP_MEMORY_LIMIT=512M

Proteger le fichier : chmod 600 .env.

docker-compose.yml complet

services:
  prestashop:
    image: prestashop/prestashop:8.1-fpm
    container_name: prestashop-app
    restart: unless-stopped
    volumes:
      - prestashop_data:/var/www/html
      - ./custom-php.ini:/usr/local/etc/php/conf.d/zz-custom.ini:ro
    environment:
      - DB_SERVER=database
      - DB_PORT=3306
      - DB_NAME=${MYSQL_DATABASE}
      - DB_USER=${MYSQL_USER}
      - DB_PASSWD=${MYSQL_PASSWORD}
      - DB_PREFIX=ps_
      - PS_DOMAIN=${PS_DOMAIN}
      - PS_LANGUAGE=fr
      - PS_COUNTRY=FR
      - PS_INSTALL_AUTO=1
      - PS_ERASE_DB=0
      - PS_FOLDER_ADMIN=${PS_FOLDER_ADMIN}
      - PS_FOLDER_INSTALL=install-rename
      - ADMIN_MAIL=${PS_ADMIN_EMAIL}
      - ADMIN_PASSWD=${PS_ADMIN_PASSWORD}
    depends_on:
      database:
        condition: service_healthy
    networks:
      - prestashop-net
    healthcheck:
      test: ["CMD", "php-fpm", "-t"]
      interval: 30s
      timeout: 5s
      retries: 3

  database:
    image: mariadb:10.11
    container_name: prestashop-db
    restart: unless-stopped
    command:
      - --character-set-server=utf8mb4
      - --collation-server=utf8mb4_unicode_ci
      - --innodb-buffer-pool-size=512M
      - --max-allowed-packet=64M
    volumes:
      - db_data:/var/lib/mysql
      - ./backups:/backups
    environment:
      - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
      - MYSQL_DATABASE=${MYSQL_DATABASE}
      - MYSQL_USER=${MYSQL_USER}
      - MYSQL_PASSWORD=${MYSQL_PASSWORD}
    healthcheck:
      test: ["CMD", "healthcheck.sh", "--su-mysql", "--connect", "--innodb_initialized"]
      interval: 10s
      timeout: 5s
      retries: 10
    networks:
      - prestashop-net

  nginx:
    image: nginx:alpine
    container_name: prestashop-nginx
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - prestashop_data:/var/www/html:ro
      - ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
      - ./nginx/ssl:/etc/nginx/ssl:ro
    depends_on:
      - prestashop
    networks:
      - prestashop-net

  redis:
    image: redis:7-alpine
    container_name: prestashop-redis
    restart: unless-stopped
    command: redis-server --maxmemory 256mb --maxmemory-policy allkeys-lru
    networks:
      - prestashop-net

volumes:
  prestashop_data:
    driver: local
  db_data:
    driver: local

networks:
  prestashop-net:
    driver: bridge

Ce qu'il faut noter

La config Nginx avec HTTP/2 et HSTS

Fichier nginx/default.conf :

upstream php-fpm {
    server prestashop-app:9000;
}

server {
    listen 80;
    server_name shop.monsite.fr;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    http2 on;
    server_name shop.monsite.fr;

    ssl_certificate /etc/nginx/ssl/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers off;

    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;

    root /var/www/html;
    index index.php;

    client_max_body_size 32M;

    gzip on;
    gzip_comp_level 6;
    gzip_types text/css application/javascript application/json image/svg+xml;

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

    location ~ \.php$ {
        fastcgi_pass php-fpm;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
        fastcgi_read_timeout 300;
        fastcgi_buffers 16 32k;
        fastcgi_buffer_size 64k;
    }

    location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff2|webp)$ {
        expires 30d;
        add_header Cache-Control "public, immutable";
        access_log off;
    }

    location ~ /\. { deny all; }
    location ~ /(install|admin-dev) { deny all; }
}

Configuration PHP-FPM custom

Fichier custom-php.ini :

memory_limit = 512M
upload_max_filesize = 32M
post_max_size = 32M
max_execution_time = 300
max_input_vars = 10000
max_input_time = 300

; OPcache pour la prod
opcache.enable = 1
opcache.memory_consumption = 256
opcache.interned_strings_buffer = 16
opcache.max_accelerated_files = 20000
opcache.validate_timestamps = 0
opcache.jit_buffer_size = 128M
opcache.jit = tracing

Generer le certificat SSL Let's Encrypt

Plusieurs approches. La plus simple : certbot sur l'hote, montage des certificats dans le conteneur Nginx :

sudo apt install certbot
sudo systemctl stop nginx 2>/dev/null
docker compose stop nginx

sudo certbot certonly --standalone -d shop.monsite.fr -m admin@monsite.fr --agree-tos

# Copier dans le repertoire monte
sudo cp /etc/letsencrypt/live/shop.monsite.fr/fullchain.pem nginx/ssl/
sudo cp /etc/letsencrypt/live/shop.monsite.fr/privkey.pem nginx/ssl/
sudo chown -R $USER: nginx/ssl/

docker compose up -d nginx

Pour le renouvellement auto, voir certificat SSL wildcard avec Let's Encrypt.

Lancer la stack et installer PrestaShop

docker compose up -d
docker compose logs -f prestashop
docker compose ps

L'installation auto prend 2 a 4 minutes. Ensuite la boutique est sur https://shop.monsite.fr et l'admin sur https://shop.monsite.fr/admin-9k3xp (selon PS_FOLDER_ADMIN).

Verifier que les conteneurs sont en mode healthy :

docker compose ps
# NAME             IMAGE                              STATUS
# prestashop-app   prestashop/prestashop:8.1-fpm     Up (healthy)
# prestashop-db    mariadb:10.11                      Up (healthy)
# prestashop-nginx nginx:alpine                       Up
# prestashop-redis redis:7-alpine                     Up

Commandes utiles au quotidien

# Shell dans le conteneur app
docker compose exec prestashop bash

# Logs en suivi
docker compose logs -f --tail=100 prestashop

# Backup de la base
docker compose exec -T database \
  mysqldump -u ps_user -p${MYSQL_PASSWORD} prestashop \
  | zstd > backups/ps_$(date +%Y%m%d).sql.zst

# Restaurer un dump
zstd -d < backups/ps_20260507.sql.zst | \
  docker compose exec -T database mysql -u ps_user -p${MYSQL_PASSWORD} prestashop

# Mise a jour de l'image PrestaShop
docker compose pull prestashop
docker compose up -d prestashop

# Voir les ressources utilisees
docker stats

# Tout arreter sans toucher aux volumes
docker compose down

# DANGER : tout supprimer y compris les donnees
# docker compose down -v

Activer le cache Redis dans PrestaShop

Le service Redis est dans la stack mais il faut l'activer cote PrestaShop. Dans app/config/parameters.php ou via l'admin > Parametres avancees > Performances :

'redis_servers' => [
    [
        'host' => 'redis',
        'port' => 6379,
        'auth' => '',
        'db' => 0,
    ],
],

Gain mesure sur un client : -45% sur le temps de generation des pages categories grace au cache des objets PrestaShop.

Backup automatise quotidien

Script scripts/backup.sh :

#!/bin/bash
set -euo pipefail
set -a; source /opt/prestashop-docker/.env; set +a

cd /opt/prestashop-docker
DATE=$(date +%Y%m%d_%H%M)

docker compose exec -T database \
  mysqldump -u ps_user -p${MYSQL_PASSWORD} \
  --single-transaction --routines --triggers \
  prestashop | zstd -T0 > backups/db_${DATE}.sql.zst

# Backup des fichiers uploades (img/, modules custom)
docker run --rm -v prestashop-docker_prestashop_data:/data \
  -v $(pwd)/backups:/backups alpine \
  tar -czf /backups/files_${DATE}.tar.gz -C /data img upload

find backups -name "*.zst" -mtime +14 -delete
find backups -name "*.tar.gz" -mtime +14 -delete

Dans cron :

0 3 * * * /opt/prestashop-docker/scripts/backup.sh >> /var/log/ps-backup.log 2>&1

Erreurs courantes et leur fix

1. "DB connection refused" au demarrage

La base n'est pas encore prete. Le depends_on healthy resout la majorite des cas. Sinon ajuster le retries du healthcheck a 15 et interval a 5s.

2. Page blanche apres install, rien dans les logs Nginx

Logs de l'app PHP :

docker compose logs prestashop | tail -50
docker compose exec prestashop tail -f /var/www/html/var/logs/dev.log

C'est presque toujours memory_limit trop bas ou un module qui plante. Augmenter dans custom-php.ini puis docker compose restart prestashop.

3. "413 Request Entity Too Large" sur upload de produits

Les 3 limites a synchroniser :

4. SSL expire et site inaccessible

Certbot tourne sur l'hote, pas dans le conteneur. Hook de renouvellement auto :

sudo nano /etc/letsencrypt/renewal-hooks/deploy/copy-to-docker.sh
#!/bin/bash
cp /etc/letsencrypt/live/shop.monsite.fr/*.pem /opt/prestashop-docker/nginx/ssl/
docker compose -f /opt/prestashop-docker/docker-compose.yml restart nginx

5. Pas d'envoi d'email depuis le conteneur

L'image officielle ne contient pas de MTA. Soit configurer SMTP externe (recommande) dans PrestaShop > Parametres avancees > Email :

Serveur SMTP : smtp.sendgrid.net
Utilisateur : apikey
Mot de passe : SG.xxxxxxxxxxxx
Port : 587
Chiffrement : TLS

Soit ajouter un service mailhog ou mailpit dans le compose pour le dev. Ne JAMAIS utiliser sendmail local en production : delivrabilite catastrophique. Cf configurer SPF DKIM DMARC.

6. Volumes accidentellement supprimes

J'ai failli faire la boulette une fois sur un serveur client a Marseille avec docker compose down -v. JAMAIS de -v sur la prod sans backup verifie.

En cas de drame, restaurer depuis le dernier dump et le tar.gz des fichiers.

Mise a l'echelle : load balancer et workers

Quand le trafic monte (>2000 visiteurs simultanes), le single-container PHP-FPM montre ses limites. Plusieurs strategies :

Augmenter les workers FPM

Dans custom-php-fpm.conf monte dans le conteneur :

pm = dynamic
pm.max_children = 30
pm.start_servers = 8
pm.min_spare_servers = 4
pm.max_spare_servers = 12
pm.max_requests = 500

Regle empirique : max_children = (RAM dispo / memoire moyenne par worker). Sur 8 Go avec 50 Mo par worker, viser 30-40 workers max.

Repliquer le service

Docker Compose en mode swarm ou plusieurs replicas avec un haproxy devant :

prestashop:
  deploy:
    replicas: 3
    resources:
      limits:
        cpus: '2'
        memory: 2G

Le probleme : sessions et cache local. Solution : sessions dans Redis, cache en Redis, uploads sur volume partage NFS ou S3-compatible.

Sortir la base sur un VPS dedie

Des que la base depasse 5 Go ou que le CPU tourne a plus de 70% en pic, je sors MariaDB sur un autre VPS. Modification minimale :

services:
  prestashop:
    environment:
      - DB_SERVER=db.interne.monsite.fr
      - DB_PORT=3306

Et on supprime le service database du compose. Bonus : la base est sauvegardable independamment.

Pour aller plus loin

Logs et debug dans la stack Docker

Le principal piege quand on debute avec Docker : ne pas savoir ou sont les logs. Reflexes :

# Logs d'un service
docker compose logs prestashop
docker compose logs --tail=100 -f prestashop

# Logs de tous les services en parallele
docker compose logs -f --tail=50

# Filtrer une periode
docker compose logs --since 30m prestashop

# Logs dans le conteneur (logs applicatifs PrestaShop)
docker compose exec prestashop tail -f /var/www/html/var/logs/prod.log

# Stats temps reel
docker stats --no-stream

Pour la persistance, j'envoie tous les logs vers le syslog de l'hote en ajoutant dans chaque service :

logging:
  driver: "json-file"
  options:
    max-size: "10m"
    max-file: "5"

Sans cette config, les logs Docker peuvent grossir indefiniment et remplir le disque. Cf logs Linux : ou chercher.

Une stack PrestaShop reproductible en 30 minutes

Une fois que vous avez gouter a Docker pour PrestaShop, le retour en arriere est tres dur. Tout est versionne dans Git (sauf .env et nginx/ssl/), reproductible sur n'importe quelle machine, et la migration vers un autre VPS prend 30 minutes : tar du dossier, restore sur la nouvelle machine, docker compose up -d. Pour les gros sites a fort trafic, vous devrez tuner Redis, ajouter Varnish devant Nginx, ou eclater la base sur un serveur dedie. Mais pour 90% des boutiques que je gere, cette stack tient sans broncher des annees.

# Articles similaires

Sur les memes sujets et plus loin