Skip to content

Kazibuya/Inception

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

14 Commits
 
 
 
 
 
 

Repository files navigation

🐳 Tutoriel Complet : Projet Inception (42)

Par un étudiant de 42 pour les étudiants de 42

Salut ! Si tu lis ce tuto, c'est que tu t'apprêtes à te lancer dans Inception, ou que tu es en plein dedans et que tu galères. Je suis passé par là, et je vais t'expliquer TOUT mon code ligne par ligne, comme si on codait ensemble.


📋 Table des matières

  1. Introduction et Architecture
  2. Le Makefile
  3. Docker Compose
  4. Services Obligatoires
  5. Services Bonus
  6. Gestion des Secrets
  7. Conseils et Pièges

🎯 Introduction et Architecture

C'est quoi Inception ?

Inception, c'est un projet de la branche System Administration de 42. Le but : monter une infrastructure complète avec Docker, en créant tes propres images (pas de Docker Hub autorisé !), avec plusieurs services qui communiquent entre eux.

Mon architecture

Voici comment j'ai organisé mon projet :

.
├── Makefile                    # Orchestration du projet
├── .env                        # Variables d'environnement (gitignore!)
├── secrets/                    # Mots de passe (gitignore!)
│   ├── db_password.txt
│   ├── db_root_password.txt
│   ├── wp_admin_password.txt
│   ├── wp_user_password.txt
│   ├── ftp_password.txt
│   └── portainer_password.txt
└── srcs/
    ├── docker-compose.yml      # Orchestration des conteneurs
    └── requirements/
        ├── nginx/              # Reverse proxy HTTPS
        ├── wordpress/          # Site WordPress + PHP-FPM
        ├── mariadb/            # Base de données
        └── bonus/
            ├── redis/          # Cache WordPress
            ├── ftp/            # Serveur FTP
            ├── adminer/        # Interface DB web
            ├── static/         # Site statique
            └── portainer/      # UI Docker

Les choix techniques

  • Alpine Linux 3.22 : légère, rapide, parfaite pour Docker
  • Volumes bind : les données persistent sur /home/namichel/data/
  • Secrets Docker : sécurité des mots de passe
  • Network bridge : tous les services communiquent via un réseau privé

🛠️ Le Makefile

Le Makefile, c'est ton chef d'orchestre. C'est lui qui va setup tout le projet, générer les certificats SSL, et lancer/arrêter tes conteneurs.

# Variables de projet
NAME			= inception
SRCS_DIR		= ./srcs
DOCKER_COMPOSE	= docker compose -f $(SRCS_DIR)/docker-compose.yml

Pourquoi ces variables ?

  • NAME : juste pour la cohérence
  • SRCS_DIR : pour pas répéter ./srcs partout
  • DOCKER_COMPOSE : le chemin complet vers ton docker-compose.yml
# Chemins des données sur l'hôte
DATA_PATH       = /home/namichel/data
WP_DATA         = $(DATA_PATH)/wordpress
DB_DATA         = $(DATA_PATH)/mariadb
PORTAINER_DATA  = $(DATA_PATH)/portainer
CERTS_DATA      = $(DATA_PATH)/certs
SECRETS_DIR		= ./secrets

Astuce importante : Change /home/namichel par ton login ! Tu peux utiliser $(HOME) si tu veux que ce soit dynamique.

Ces dossiers vont contenir toutes tes données persistantes :

  • WP_DATA : les fichiers WordPress (thèmes, plugins, uploads)
  • DB_DATA : les fichiers de la base de données MariaDB
  • PORTAINER_DATA : config de Portainer
  • CERTS_DATA : certificats SSL
  • SECRETS_DIR : fichiers secrets (mots de passe)
# Couleurs
GREEN           = \033[0;32m
RED             = \033[0;31m
YELLOW          = \033[0;33m
RESET           = \033[0m

Les couleurs, c'est juste pour rendre le Makefile sexy dans le terminal. Vert = succès, Rouge = destruction, Jaune = info.

Règle all

# Règle par défaut
all: setup certs
	@echo "$(GREEN)Démarrage de l'infrastructure Alpine...$(RESET)"
	@$(DOCKER_COMPOSE) up -d --build

Ligne par ligne :

  • all: setup certs : avant de lancer les conteneurs, on fait setup et certs
  • @echo : affiche un message (@ = pas d'affichage de la commande elle-même)
  • up -d --build : lance les conteneurs en arrière-plan et rebuild les images

Règle setup

setup:
	@echo "$(YELLOW)Préparation des dossiers de volumes...$(RESET)"
	@mkdir -p $(WP_DATA)
	@mkdir -p $(DB_DATA)
	@mkdir -p $(PORTAINER_DATA)
	@mkdir -p $(CERTS_DATA)
	@mkdir -p $(SECRETS_DIR)

Pourquoi mkdir -p ?

  • -p : crée les dossiers parents si nécessaire, et ne renvoie pas d'erreur si le dossier existe déjà
	@if [ ! -f $(SECRETS_DIR)/db_password.txt ]; then echo "db_password" > $(SECRETS_DIR)/db_password.txt; fi
	@if [ ! -f $(SECRETS_DIR)/db_root_password.txt ]; then echo "db_root_password" > $(SECRETS_DIR)/db_root_password.txt; fi
	@if [ ! -f $(SECRETS_DIR)/portainer_password.txt ]; then echo "pass123456789" > $(SECRETS_DIR)/portainer_password.txt; fi
	@if [ ! -f $(SECRETS_DIR)/wp_user_password.txt ]; then echo "123" > $(SECRETS_DIR)/wp_user_password.txt; fi
	@if [ ! -f $(SECRETS_DIR)/wp_admin_password.txt ]; then echo "123" > $(SECRETS_DIR)/wp_admin_password.txt; fi
	@if [ ! -f $(SECRETS_DIR)/ftp_password.txt ]; then echo "123" > $(SECRETS_DIR)/ftp_password.txt; fi

Ces lignes créent les fichiers secrets avec des valeurs par défaut.

⚠️ ATTENTION : Ces mots de passe par défaut sont pourris ! Change-les avant de passer à la correction.

La logique :

  • if [ ! -f fichier ] : si le fichier n'existe pas
  • then echo "valeur" > fichier : créer le fichier avec la valeur
  • fi : fin du if

Pourquoi cette approche ?

  • Tu lances make et ça marche direct (pratique pour tester)
  • Mais en correction, tu devras montrer que tu utilises des vrais mots de passe complexes
  • Tu peux créer les fichiers manuellement avant le make pour override ces valeurs

Règle certs

certs:
	@if [ ! -f $(CERTS_DATA)/inception.crt ]; then \
		echo "$(YELLOW)Génération des certificats SSL auto-signés...$(RESET)"; \
		openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
			-keyout $(CERTS_DATA)/inception.key \
			-out $(CERTS_DATA)/inception.crt \
			-subj "/C=FR/ST=Angouleme/L=Angouleme/O=42/OU=namichel/CN=namichel.42.fr"; \
		chmod 644 $(CERTS_DATA)/inception.crt; \
		chmod 600 $(CERTS_DATA)/inception.key; \
	else \
		echo "$(GREEN)Certificats déjà présents.$(RESET)"; \
	fi

Décortiquons la commande openssl :

  • req : crée une demande de certificat
  • -x509 : génère un certificat auto-signé (pas besoin d'autorité de certification)
  • -nodes : pas de mot de passe sur la clé privée
  • -days 365 : valide 1 an
  • -newkey rsa:2048 : crée une nouvelle clé RSA de 2048 bits
  • -keyout : où sauver la clé privée
  • -out : où sauver le certificat
  • -subj : les infos du certificat (Country, State, Locality, Organization, Organizational Unit, Common Name)

Pourquoi CN=namichel.42.fr ? C'est le nom de domaine que tu vas utiliser. Le CN (Common Name) doit correspondre à ton server_name dans nginx.

Les permissions :

  • 644 pour le .crt : tout le monde peut lire, seul root peut écrire
  • 600 pour le .key : seul root peut lire/écrire (sécurité!)

Règles de nettoyage

down:
	@echo "$(RED)Arrêt des conteneurs...$(RESET)"
	@$(DOCKER_COMPOSE) down

Simple : arrête tous les conteneurs.

clean: down
	@echo "$(RED)Suppression des images inutilisées...$(RESET)"
	@docker system prune -a -f
  • down : d'abord on arrête tout
  • system prune -a -f : supprime toutes les images non utilisées (-a), sans confirmation (-f)
fclean: clean
	@echo "$(RED)Suppression des volumes et certificats sur l'hôte...$(RESET)"
	@sudo rm -rf $(DATA_PATH)
	@sudo rm -rf $(SECRETS_DIR)
	@if [ $$(docker volume ls -q | wc -l) -gt 0 ]; then \
		docker volume rm $$(docker volume ls -q); \
	fi

Le grand nettoyage :

  • clean : déjà fait juste avant
  • rm -rf $(DATA_PATH) : supprime TOUTES les données persistantes (d'où le sudo)
  • rm -rf $(SECRETS_DIR) : supprime tous les secrets
  • La condition if : si y'a des volumes Docker (docker volume ls -q | wc -l), on les supprime tous

⚠️ DANGER : fclean détruit TOUT. Tu perds ta base de données, tes fichiers WordPress, tout. À utiliser seulement pour repartir de zéro.

re: fclean all

Le classique : nettoyage total puis reconstruction.

.PHONY: all setup certs down clean fclean re

.PHONY : indique que ces règles ne correspondent pas à des fichiers réels. Sans ça, si tu avais un fichier nommé "clean", make serait perdu.


🐋 Docker Compose

Le docker-compose.yml, c'est le cœur du projet. Il définit tous tes services, leurs relations, leurs volumes, leurs secrets.

Anchor (config commune)

x-config: &config
 restart: on-failure:3
 stdin_open: true
 tty: true
 networks:
 - inception

C'est quoi ce truc bizarre ?

x-config est une anchor YAML. C'est comme un template qu'on va réutiliser.

  • restart: on-failure:3 : si le conteneur crash, Docker le redémarre max 3 fois
  • stdin_open: true : garde stdin ouvert (comme docker run -i)
  • tty: true : alloue un pseudo-TTY (comme docker run -t)
  • networks: - inception : tous les services seront sur le réseau inception

Pourquoi -i -t ? Pour pouvoir faire docker exec -it et avoir un shell interactif. Pratique pour le debug.

Service MariaDB

services:
 mariadb:
  <<: *config

<<: *config = "copie-colle la config de l'anchor ici". Tous les services auront cette config de base.

  build: requirements/mariadb
  container_name: mariadb
  • build : où trouver le Dockerfile
  • container_name : nom du conteneur (sinon Docker génère un nom random)
  volumes:
   - mariadb_data:/var/lib/mysql

Volume bind : /home/namichel/data/mariadb (sur l'hôte) → /var/lib/mysql (dans le conteneur)

Pourquoi /var/lib/mysql ? C'est là que MariaDB stocke ses bases de données par défaut.

  env_file:
   - ../.env

Charge les variables d'environnement depuis .env (à la racine du projet).

  secrets:
   - db_password
   - db_root_password

Monte les secrets dans /run/secrets/ du conteneur. On verra ça plus tard.

Service WordPress

 wordpress:
  <<: *config
  build: requirements/wordpress
  container_name: wordpress
  volumes:
   - wordpress_data:/var/www/html

Même logique que MariaDB, mais les fichiers WordPress vont dans /var/www/html.

  env_file:
   - ../.env
  secrets:
   - db_password
   - wp_user_password
   - wp_admin_password
  depends_on:
   - mariadb
   - redis

depends_on : Docker va démarrer mariadb et redis AVANT wordpress.

⚠️ ATTENTION : depends_on garantit l'ordre de démarrage, mais pas que MariaDB soit prête à accepter des connexions. C'est pour ça qu'on fait une boucle while dans le script de setup de WordPress.

Service NGINX

 nginx:
  <<: *config
  build: requirements/nginx
  container_name: nginx
  volumes:
   - wordpress_data:/var/www/html
   - certs:/etc/nginx/ssl

2 volumes :

  • wordpress_data : nginx a besoin d'accéder aux fichiers PHP de WordPress
  • certs : les certificats SSL générés par le Makefile
  ports:
   - "443:443"

Port mapping : 443 de l'hôte → 443 du conteneur.

Pourquoi seulement 443 ? Le sujet impose HTTPS uniquement (TLS 1.2 ou 1.3). Pas de HTTP en clair.

  depends_on:
   - wordpress
   - static

Nginx démarre après WordPress et le site statique.

Services Bonus

 adminer:
  <<: *config
  build: requirements/bonus/adminer
  container_name: adminer
  depends_on:
   - mariadb

Adminer : une interface web pour gérer la base de données. Pratique pour vérifier que MariaDB fonctionne.

 static:
  <<: *config
  build: requirements/bonus/static
  container_name: static

Un site statique tout simple (juste du HTML).

 redis:
  <<: *config
  build: requirements/bonus/redis
  container_name: redis

Redis : un cache en mémoire pour accélérer WordPress.

 ftp:
  <<: *config
  build: requirements/bonus/ftp
  container_name: ftp
  env_file:
   - ../.env
  volumes:
   - wordpress_data:/var/www/html
   - certs:/etc/nginx/ssl
  ports:
   - "21:21"
   - "40000-40005:40000-40005"

FTP avec SSL (FTPS) :

  • Port 21 : contrôle FTP
  • Ports 40000-40005 : mode passif (pour les transferts de données)
  • Volume wordpress_data : pour modifier les fichiers WordPress via FTP
  • Volume certs : pour FTPS (FTP sécurisé)
  secrets:
   - ftp_password

Le mot de passe FTP est stocké dans un secret.

 portainer:
  <<: *config
  build: requirements/bonus/portainer
  container_name: portainer
  environment:
   - DOCKER_API_VERSION=1.43
  privileged: true
  user: root

Portainer : une UI pour gérer Docker.

  • DOCKER_API_VERSION=1.43 : version de l'API Docker

⚠️ PROBLÈME RÉCENT (Docker 29.0.0+) : Docker Engine 29.0.0 a introduit des breaking changes. Si Portainer refuse de se connecter, c'est probablement à cause de ça.

Solutions :

  1. Downgrade Docker vers une version stable (28.x)
  2. Forcer une API version plus ancienne au niveau système (voir section "Erreurs courantes")
  3. Spécifier l'API version dans le docker-compose (déjà fait ici avec DOCKER_API_VERSION=1.43)
  • privileged: true : accès complet au système (nécessaire pour Portainer)
  • user: root : run en tant que root
  ports:
   - "9443:9443"

Portainer écoute sur le port 9443 (HTTPS).

  volumes:
   - /var/run/docker.sock:/var/run/docker.sock
   - portainer_data:/data

/var/run/docker.sock : le socket Docker de l'hôte. Portainer en a besoin pour communiquer avec le daemon Docker.

⚠️ DANGER : donner accès au socket Docker = donner les clés du royaume. Portainer peut créer/détruire n'importe quel conteneur.

  secrets:
   - portainer_password

Déclaration des Secrets

secrets:
 db_password:
  file: ../secrets/db_password.txt
 db_root_password:
  file: ../secrets/db_root_password.txt
 portainer_password:
  file: ../secrets/portainer_password.txt
 wp_user_password:
  file: ../secrets/wp_user_password.txt
 wp_admin_password:
  file: ../secrets/wp_admin_password.txt
 ftp_password:
  file: ../secrets/ftp_password.txt

Docker secrets : Docker monte ces fichiers en read-only dans /run/secrets/ de chaque conteneur qui les déclare.

Pourquoi utiliser des secrets ?

  • Sécurité : pas de mots de passe dans les variables d'environnement (visibles avec docker inspect)
  • Pas de mots de passe hardcodés dans les Dockerfiles
  • Respect des bonnes pratiques

Réseau

networks:
 inception:
  driver: bridge

Network bridge : tous les conteneurs peuvent se parler par leur nom de service.

Exemple : WordPress peut faire mariadb:3306 pour se connecter à MariaDB.

Volumes

volumes:
 wordpress_data:
  driver: local
  driver_opts:
   type: none
   o: bind
   device: /home/namichel/data/wordpress

Bind mount : lie un dossier de l'hôte à un volume Docker.

  • type: none : pas de filesystem Docker, on utilise le filesystem de l'hôte
  • o: bind : bind mount
  • device : chemin absolu sur l'hôte

Pourquoi bind mount et pas volume Docker classique ? Le sujet impose que les volumes soient sur /home/login/data. Avec un volume Docker classique, Docker choisirait lui-même l'emplacement (genre /var/lib/docker/volumes/).

Les autres volumes (mariadb_data, certs, portainer_data) suivent la même logique.


📦 Services Obligatoires

NGINX

NGINX est ton reverse proxy. Il reçoit les requêtes HTTPS sur le port 443 et les dispatche vers les bons services.

Dockerfile

FROM alpine:3.22

Alpine 3.22 : version stable et légère.

RUN apk update && apk upgrade && \
	apk add --no-cache nginx openssl

Ligne par ligne :

  • apk update : met à jour la liste des paquets disponibles
  • apk upgrade : met à jour les paquets installés
  • apk add --no-cache : installe des paquets sans garder le cache (image plus légère)
    • nginx : le serveur web
    • openssl : pour générer/manipuler des certificats SSL (même si on les génère avec le Makefile, c'est une bonne pratique)

Pourquoi && et \ ?

  • && : exécute la commande suivante seulement si la précédente réussit
  • \ : continue la commande sur la ligne suivante (pour la lisibilité)
RUN mkdir -p /run/nginx

Crée le dossier où nginx va stocker son PID. Sans ça, nginx refuse de démarrer.

RUN ln -sf /dev/stdout /var/log/nginx/access.log && \
	ln -sf /dev/stderr /var/log/nginx/error.log

Redirection des logs vers stdout/stderr.

  • ln -sf : crée un lien symbolique (-s) en forçant (-f)
  • /dev/stdout : sortie standard
  • /dev/stderr : sortie d'erreur

Pourquoi faire ça ? Docker capture stdout/stderr. En redirigeant les logs nginx vers stdout/stderr, tu peux les voir avec docker logs nginx.

COPY conf/nginx.conf /etc/nginx/http.d/default.conf

Copie ta config nginx personnalisée.

Pourquoi /etc/nginx/http.d/ et pas /etc/nginx/nginx.conf ? Sur Alpine, nginx charge automatiquement tous les fichiers .conf dans /etc/nginx/http.d/. C'est plus propre de mettre ta config là-dedans.

EXPOSE 443

Indique que le conteneur écoute sur le port 443. C'est purement informatif, ça ne fait pas le port mapping (c'est dans le docker-compose).

CMD ["nginx", "-g", "daemon off;"]

Lance nginx en foreground.

  • -g "daemon off;" : force nginx à rester en premier plan

Pourquoi daemon off ? Docker a besoin d'un processus qui tourne en foreground. Si nginx se lance en daemon (arrière-plan), Docker pense que le conteneur est fini et l'arrête.

Configuration NGINX

server {
	listen 443 ssl;
	server_name namichel.42.fr;

Bloc server : définit un virtual host.

  • listen 443 ssl : écoute sur le port 443 avec SSL/TLS
  • server_name : le nom de domaine (doit correspondre au CN du certificat)

N'oublie pas d'ajouter namichel.42.fr dans /etc/hosts !

127.0.0.1 namichel.42.fr
	ssl_certificate /etc/nginx/ssl/inception.crt;
	ssl_certificate_key /etc/nginx/ssl/inception.key;
	ssl_protocols TLSv1.2 TLSv1.3;

Config SSL :

  • ssl_certificate : chemin vers le certificat
  • ssl_certificate_key : chemin vers la clé privée
  • ssl_protocols : protocoles autorisés (le sujet impose TLSv1.2 ou 1.3)
	root /var/www/html;
	index index.php;
  • root : dossier racine des fichiers web
  • index : fichier par défaut (ici, index.php de WordPress)
	location / {
		try_files $uri $uri/ /index.php?$args;
	}

Bloc location / : gère toutes les requêtes.

try_files $uri $uri/ /index.php?$args; :

  1. Essaye de servir le fichier tel quel ($uri)
  2. Si c'est un dossier, essaye de servir l'index ($uri/)
  3. Sinon, redirige vers index.php (pour les permaliens WordPress)
	location ~ \.php$ {
		include fastcgi_params;
		fastcgi_pass wordpress:9000;
		fastcgi_index index.php;
		fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
	}

Bloc location ~ \.php$ : gère les fichiers PHP.

  • ~ : regex
  • \.php$ : fichiers qui se terminent par .php

FastCGI :

  • include fastcgi_params : charge les paramètres FastCGI standards
  • fastcgi_pass wordpress:9000 : transmet la requête au service wordpress sur le port 9000
  • fastcgi_index index.php : fichier index par défaut
  • fastcgi_param SCRIPT_FILENAME : chemin complet du script PHP à exécuter

Pourquoi wordpress:9000 ? Dans le réseau Docker inception, les services communiquent par leur nom. wordpress est le nom du service dans le docker-compose, et PHP-FPM écoute sur le port 9000.

	location /adminer {
		proxy_pass http://adminer:8080/;
		proxy_set_header Host $host;
	}

Proxy vers Adminer.

Quand tu vas sur https://namichel.42.fr/adminer, nginx redirige vers http://adminer:8080/.

	location /static/ {
		proxy_pass http://static:8081/;
		proxy_set_header Host $host;
	}

Même chose pour le site statique.

	location /portainer/ {
		proxy_pass https://portainer:9443/;
		proxy_set_header Upgrade $http_upgrade;
		proxy_set_header Connection "upgrade";
	}

Proxy vers Portainer (avec WebSocket).

  • Upgrade et Connection "upgrade" : nécessaires pour les WebSockets (Portainer les utilise pour les mises à jour en temps réel)

WordPress + PHP-FPM

WordPress, c'est le CMS (Content Management System). PHP-FPM (FastCGI Process Manager) exécute le code PHP.

Dockerfile

FROM alpine:3.22
RUN apk update && apk upgrade && \
	apk add --no-cache php84 php84-fpm php84-mysqli php84-phar php84-mbstring wget mariadb-client php84-pecl-redis php84-tokenizer php84-ctype

Paquets installés :

  • php84 : PHP 8.4
  • php84-fpm : PHP-FPM (pour communiquer avec nginx via FastCGI)
  • php84-mysqli : extension MySQL/MariaDB pour PHP
  • php84-phar : pour manipuler les archives PHAR (nécessaire pour WP-CLI)
  • php84-mbstring : gestion des strings multi-bytes (requis par WordPress)
  • wget : pour télécharger WP-CLI
  • mariadb-client : client MySQL (pour tester la connexion à MariaDB)
  • php84-pecl-redis : extension Redis pour PHP (pour le cache WordPress)
  • php84-tokenizer : tokenizer PHP (requis par WP-CLI)
  • php84-ctype : extension ctype (requis par WordPress)
RUN ln -s /usr/bin/php84 /usr/bin/php

Crée un lien symbolique phpphp84. Comme ça, tu peux taper php au lieu de php84.

RUN wget https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar \
	&& chmod +x wp-cli.phar \
	&& mv wp-cli.phar /usr/local/bin/wp

Installation de WP-CLI (WordPress Command Line Interface).

  1. Télécharge l'archive PHAR
  2. Rend le fichier exécutable
  3. Le déplace dans /usr/local/bin/wp (pour pouvoir faire wp dans le terminal)

Pourquoi WP-CLI ? WP-CLI permet d'installer et configurer WordPress en ligne de commande. C'est bien plus simple que de le faire manuellement.

COPY conf/www.conf /etc/php84/php-fpm.d/www.conf

Copie ta config PHP-FPM personnalisée.

WORKDIR /var/www/html

Définit le dossier de travail (tous les chemins relatifs partiront de là).

COPY conf/setup.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/setup.sh

Copie et rend exécutable le script de setup.

EXPOSE 9000

PHP-FPM écoute sur le port 9000 (pour communiquer avec nginx via FastCGI).

ENTRYPOINT ["/usr/local/bin/setup.sh"]

ENTRYPOINT vs CMD :

  • ENTRYPOINT : commande principale qui sera toujours exécutée
  • CMD : arguments par défaut (peuvent être overridés)

Ici, on veut que le script setup.sh s'exécute à chaque démarrage du conteneur.

Configuration PHP-FPM (www.conf)

[www]
user = PHP_USER_REPLACE
group = www-data

Pool PHP-FPM.

  • user : l'utilisateur qui exécute PHP-FPM (on va le remplacer dynamiquement dans le script)
  • group : le groupe

Pourquoi PHP_USER_REPLACE ? C'est un placeholder. Le script setup.sh va le remplacer par la vraie valeur de $PHP_USER (définie dans le .env).

listen = 9000

PHP-FPM écoute sur le port 9000.

pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3

Process Manager :

  • pm = dynamic : le nombre de processus s'adapte dynamiquement
  • max_children = 5 : max 5 processus PHP en même temps
  • start_servers = 2 : démarre avec 2 processus
  • min_spare_servers = 1 : garde au moins 1 processus en idle
  • max_spare_servers = 3 : garde max 3 processus en idle

Pourquoi ces valeurs ? Pour un projet comme Inception (usage local/dev), ces valeurs sont largement suffisantes. En prod, tu augmenterais max_children.

catch_workers_output = yes

Capture les sorties des workers PHP (utile pour le debug).

Script de setup (setup.sh)

#!/bin/sh

Shebang : indique que c'est un script shell.

addgroup -S www-data 2>/dev/null || true

Création du groupe www-data.

  • addgroup -S : crée un groupe système
  • 2>/dev/null : redirige les erreurs vers le néant
  • || true : retourne toujours un succès (même si le groupe existe déjà)
if ! id "$PHP_USER" > /dev/null 2>&1; then
	deluser www-data 2>/dev/null
	adduser -D -u 82 -G www-data "$PHP_USER"
fi

Gestion de l'utilisateur PHP.

  • if ! id "$PHP_USER" : si l'utilisateur n'existe pas
  • deluser www-data : supprime l'utilisateur par défaut
  • adduser -D -u 82 -G www-data "$PHP_USER" : crée l'utilisateur
    • -D : pas de mot de passe
    • -u 82 : UID 82 (standard pour www-data)
    • -G www-data : ajoute au groupe www-data

Pourquoi UID 82 ? C'est l'UID standard de www-data. Ça évite les problèmes de permissions avec nginx (qui tourne aussi en www-data).

chown -R "$PHP_USER":www-data /var/www/html

Change le propriétaire de tous les fichiers WordPress.

Pourquoi c'est important ? PHP-FPM doit pouvoir lire/écrire dans /var/www/html (pour les uploads, les mises à jour, etc.).

while [ ! -f /run/secrets/db_password ]; do
	echo "Waiting for secret file"
	sleep 1
done

Attente du secret.

Boucle tant que le fichier secret n'existe pas. C'est une sécurité : si Docker met du temps à monter le secret, on attend.

DB_PASS=$(cat /run/secrets/db_password)
WP_ADMIN_PASS=$(cat /run/secrets/wp_admin_password)
WP_USER_PASS=$(cat /run/secrets/wp_user_password)

Lecture des secrets.

$(cat fichier) : exécute la commande cat et stocke le résultat dans la variable.

until mariadb-admin ping -h"mariadb" --user="$SQL_USER" --password="$DB_PASS" --silent; do
	echo "wordpress waiting for Mariadb"
	sleep 2
done

Attente de MariaDB.

  • until commande; do : boucle tant que la commande échoue
  • mariadb-admin ping : envoie un "ping" à MariaDB pour vérifier qu'elle répond
  • -h"mariadb" : host (le nom du service Docker)
  • --silent : mode silencieux

C'est LA solution au problème de depends_on.

depends_on garantit juste que MariaDB démarre avant WordPress, mais pas qu'elle soit prête. Cette boucle attend que MariaDB accepte les connexions.

if [ ! -f ./wp-config.php ]; then
	echo "Wordpress is being installed"

Vérification de l'installation.

Si wp-config.php existe, WordPress est déjà installé. Sinon, on installe.

Pourquoi cette vérification ? Si tu redémarres le conteneur, tu ne veux pas réinstaller WordPress à chaque fois (tu perdrais tes données).

	php -d memory_limit=-1 /usr/local/bin/wp core download --allow-root

Téléchargement de WordPress.

  • php -d memory_limit=-1 : lance PHP sans limite de mémoire
  • wp core download : télécharge les fichiers WordPress
  • --allow-root : autorise WP-CLI à tourner en root (nécessaire dans Docker)
	wp config create \
		--dbname=$SQL_DATABASE \
		--dbuser=$SQL_USER \
		--dbpass=$DB_PASS \
		--dbhost=mariadb:3306 \
		--allow-root

Création du fichier wp-config.php.

  • --dbname : nom de la base de données
  • --dbuser : utilisateur MySQL
  • --dbpass : mot de passe
  • --dbhost=mariadb:3306 : host et port de MariaDB

Pourquoi mariadb:3306 et pas localhost ? Dans Docker, localhost = le conteneur lui-même. Pour parler à MariaDB, on utilise le nom du service (mariadb).

	wp core install \
		--url=$DOMAIN_NAME \
		--title="Inception" \
		--admin_user=$WP_ADMIN_USER \
		--admin_password=$WP_ADMIN_PASS \
		--admin_email=$WP_ADMIN_EMAIL \
		--skip-email \
		--allow-root

Installation de WordPress.

  • --url : l'URL du site (ex: https://namichel.42.fr)
  • --title : titre du site
  • --admin_user : nom de l'admin
  • --admin_password : mot de passe admin
  • --admin_email : email admin
  • --skip-email : ne pas envoyer d'email de confirmation
	wp user create $WP_USER $WP_USER_EMAIL \
		--role=author \
		--user_pass=$WP_USER_PASS \
		--allow-root

Création d'un deuxième utilisateur.

Le sujet impose d'avoir au moins 2 utilisateurs : un admin et un utilisateur normal.

  • --role=author : rôle "auteur" (peut créer/éditer ses propres articles)
	echo "Wordpress OK!"
fi

Fin du bloc if. Si wp-config.php existait déjà, tout ce bloc est sauté.

wp config set WP_REDIS_HOST redis --allow-root
wp config set WP_REDIS_PORT 6379 --raw --allow-root

Configuration de Redis dans WordPress.

  • WP_REDIS_HOST : l'host du serveur Redis (le nom du service Docker)
  • WP_REDIS_PORT : le port (6379 par défaut)
  • --raw : stocke la valeur brute (sans guillemets)
wp plugin install redis-cache --activate --allow-root

Installation et activation du plugin Redis Cache.

Ce plugin permet à WordPress d'utiliser Redis comme cache.

wp redis enable --allow-root

Activation du cache Redis.

sed -i "s/PHP_USER_REPLACE/$PHP_USER/g" /etc/php84/php-fpm.d/www.conf

Remplacement du placeholder dans la config PHP-FPM.

  • sed -i : édite le fichier en place
  • s/PHP_USER_REPLACE/$PHP_USER/g : remplace toutes les occurrences de PHP_USER_REPLACE par la valeur de $PHP_USER

Rappel : dans www.conf, on avait mis user = PHP_USER_REPLACE. Cette ligne le remplace par la vraie valeur (ex: user = www-data).

exec /usr/sbin/php-fpm84 -F

Lancement de PHP-FPM.

  • exec : remplace le processus du script par PHP-FPM
  • -F : foreground (ne pas se lancer en daemon)

Pourquoi exec ? Sans exec, le script tournerait en PID 1, et PHP-FPM en processus fils. Si le script se termine, Docker arrête le conteneur. Avec exec, PHP-FPM devient le PID 1, et le conteneur reste actif.


MariaDB

MariaDB, c'est la base de données. WordPress y stocke tous ses articles, utilisateurs, paramètres, etc.

Dockerfile

FROM alpine:3.22
RUN apk update && apk upgrade && \
	apk add --no-cache mariadb mariadb-client && \
	mkdir -p /var/run/mysqld /var/lib/mysql && \
	chown -R mysql:mysql /var/run/mysqld /var/lib/mysql

Installation de MariaDB.

  • mariadb : le serveur de base de données
  • mariadb-client : le client (pour se connecter en ligne de commande)
  • mkdir -p /var/run/mysqld /var/lib/mysql : crée les dossiers nécessaires
  • chown -R mysql:mysql : donne les permissions à l'utilisateur mysql

Pourquoi ces dossiers ?

  • /var/run/mysqld : où MariaDB stocke son socket et son PID
  • /var/lib/mysql : où MariaDB stocke les bases de données
COPY conf/mariadb-server.cnf /etc/my.cnf.d/mariadb-server.cnf

Copie ta config MariaDB personnalisée.

COPY conf/setup.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/setup.sh
EXPOSE 3306

Port MySQL standard.

ENTRYPOINT ["/usr/local/bin/setup.sh"]

Configuration MariaDB (mariadb-server.cnf)

[client]
socket = /var/run/mysqld/mysqld.sock

Section [client] : config pour les clients MySQL.

  • socket : où trouver le socket Unix
[mysqld]
skip-networking = false
bind-address = 0.0.0.0
port = 3306
user = mysql

Section [mysqld] : config du serveur.

  • skip-networking = false : autorise les connexions réseau (par défaut, MariaDB n'écoute que sur le socket Unix)
  • bind-address = 0.0.0.0 : écoute sur toutes les interfaces réseau
  • port = 3306 : port d'écoute
  • user = mysql : utilisateur qui exécute MariaDB

Pourquoi 0.0.0.0 et pas 127.0.0.1 ? Dans Docker, 127.0.0.1 = localhost du conteneur. Pour que WordPress (dans un autre conteneur) puisse se connecter, MariaDB doit écouter sur toutes les interfaces (0.0.0.0).

Script de setup (setup.sh)

#!/bin/sh
if [ ! -d "/var/lib/mysql/mysql" ]; then
	echo "MariaDB storage init..."

Vérification de l'initialisation.

Si le dossier /var/lib/mysql/mysql existe, MariaDB est déjà initialisée. Sinon, on initialise.

Pourquoi /var/lib/mysql/mysql ? mysql est une base de données système créée par MariaDB lors de l'initialisation. Si elle existe, c'est que MariaDB a déjà été initialisée.

	DB_PASS=$(cat /run/secrets/db_password)
	DB_ROOT_PASS=$(cat /run/secrets/db_root_password)

Lecture des secrets.

	mysql_install_db --user=mysql --datadir=/var/lib/mysql --skip-test-db > /dev/null

Initialisation de MariaDB.

  • mysql_install_db : script d'initialisation
  • --user=mysql : exécute en tant qu'utilisateur mysql
  • --datadir=/var/lib/mysql : où stocker les données
  • --skip-test-db : ne pas créer la base de test
  • > /dev/null : redirige la sortie vers le néant (pour ne pas polluer les logs)
	/usr/bin/mysqld --user=mysql --bootstrap << EOF

Mode bootstrap de MariaDB.

Le mode --bootstrap permet d'exécuter des commandes SQL avant le démarrage complet du serveur.

<< EOF : heredoc, tout ce qui suit jusqu'à EOF est passé en entrée de la commande.

USE mysql;
FLUSH PRIVILEGES;
  • USE mysql : sélectionne la base de données mysql
  • FLUSH PRIVILEGES : recharge les privilèges
DELETE FROM mysql.user WHERE User='';
DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1', '::1');

Nettoyage de sécurité.

  • Ligne 1 : supprime les utilisateurs anonymes (ceux sans nom)
  • Ligne 2 : supprime les comptes root distants (garde seulement les connexions locales)

Pourquoi ? Par défaut, MariaDB crée des comptes peu sécurisés. On les supprime.

ALTER USER 'root'@'localhost' IDENTIFIED BY '$DB_ROOT_PASS';

Change le mot de passe root.

IDENTIFIED BY : définit le mot de passe.

CREATE DATABASE IF NOT EXISTS $SQL_DATABASE;

Création de la base de données WordPress.

IF NOT EXISTS : ne fait rien si la base existe déjà (évite les erreurs).

CREATE USER IF NOT EXISTS '$SQL_USER'@'%' IDENTIFIED BY '$DB_PASS';

Création de l'utilisateur WordPress.

  • '$SQL_USER'@'%' : l'utilisateur peut se connecter depuis n'importe où (% = wildcard)

Pourquoi '%' et pas 'localhost' ? WordPress tourne dans un autre conteneur. Pour MariaDB, ce n'est pas localhost, c'est une connexion réseau.

GRANT ALL PRIVILEGES ON $SQL_DATABASE.* TO '$SQL_USER'@'%';
FLUSH PRIVILEGES;
EOF

Attribution des droits.

  • GRANT ALL PRIVILEGES ON $SQL_DATABASE.* : donne tous les droits sur toutes les tables de la base WordPress
  • FLUSH PRIVILEGES : applique les changements
	echo "Database and users created !"
fi

Fin du bloc if.

exec /usr/bin/mysqld --user=mysql --console

Lancement de MariaDB.

  • --user=mysql : exécute en tant qu'utilisateur mysql
  • --console : affiche les logs sur la console (stdout)

🎁 Services Bonus

Redis

Redis, c'est un cache en mémoire. WordPress l'utilise pour stocker temporairement des données fréquemment utilisées (posts, pages, requêtes), ce qui accélère le site.

Dockerfile

FROM alpine:3.22
RUN apk update && apk upgrade && \
	apk add --no-cache redis

Simple : on installe Redis.

COPY conf/redis.conf /etc/redis.conf

Copie la config Redis.

EXPOSE 6379

Port Redis standard.

CMD ["redis-server", "/etc/redis.conf"]

Lance Redis avec la config personnalisée.

Configuration Redis (redis.conf)

bind 0.0.0.0
port 6379
protected-mode no

Config minimaliste.

  • bind 0.0.0.0 : écoute sur toutes les interfaces (pour que WordPress puisse se connecter)
  • port 6379 : port par défaut
  • protected-mode no : désactive le mode protégé

Pourquoi protected-mode no ? En mode protégé, Redis refuse les connexions externes si aucun mot de passe n'est défini. Dans un réseau Docker privé, c'est safe de désactiver ce mode.


FTP

Le serveur FTP (avec SSL) permet d'uploader/modifier les fichiers WordPress depuis un client FTP (FileZilla, etc.).

Dockerfile

FROM alpine:3.22
RUN apk add --no-cache vsftpd openssl shadow

Paquets installés :

  • vsftpd : Very Secure FTP Daemon
  • openssl : pour SSL/TLS (FTPS)
  • shadow : pour gérer les utilisateurs/mots de passe
COPY conf/vsftpd.conf /etc/vsftpd.conf
COPY conf/setup.sh /usr/local/bin/setup.sh
RUN chmod +x /usr/local/bin/setup.sh
EXPOSE 21 40000-40005

Ports FTP :

  • 21 : port de contrôle (commandes)
  • 40000-40005 : ports passifs (transferts de données)
ENTRYPOINT ["/usr/local/bin/setup.sh"]

Configuration FTP (vsftpd.conf)

listen=YES
listen_ipv6=NO
  • listen=YES : vsftpd écoute en IPv4
  • listen_ipv6=NO : désactive IPv6 (pas nécessaire pour Inception)
local_enable=YES
write_enable=YES
  • local_enable=YES : autorise les utilisateurs locaux à se connecter
  • write_enable=YES : autorise l'upload
chroot_local_user=YES
allow_writeable_chroot=YES
local_root=/var/www/html

Chroot (prison) :

  • chroot_local_user=YES : enferme l'utilisateur dans son dossier home
  • allow_writeable_chroot=YES : autorise l'écriture dans le chroot (normalement interdit par sécurité)
  • local_root=/var/www/html : dossier racine du chroot

Pourquoi chroot ? Pour éviter que l'utilisateur FTP puisse naviguer dans tout le filesystem. Il sera limité à /var/www/html.

seccomp_sandbox=NO

Désactive le sandbox seccomp (sinon vsftpd crash sur Alpine).

local_umask=022

Umask : permissions par défaut des fichiers créés.

022 = les fichiers créés auront les permissions 644 (rw-r--r--).

pasv_enable=YES
pasv_min_port=40000
pasv_max_port=40005
pasv_address=0.0.0.0

Mode passif :

  • pasv_enable=YES : active le mode passif (nécessaire pour les connexions derrière un firewall)
  • pasv_min_port et pasv_max_port : plage de ports pour les transferts
  • pasv_address=0.0.0.0 : adresse IP publique (ici, n'importe quelle interface)
ssl_enable=YES
allow_anon_ssl=NO
force_local_data_ssl=YES
force_local_logins_ssl=YES
ssl_tlsv1=YES
ssl_sslv2=NO
ssl_sslv3=NO

Configuration SSL (FTPS) :

  • ssl_enable=YES : active SSL
  • allow_anon_ssl=NO : pas de SSL pour les connexions anonymes
  • force_local_data_ssl=YES : force SSL pour les transferts de données
  • force_local_logins_ssl=YES : force SSL pour l'authentification
  • ssl_tlsv1=YES : autorise TLS 1.x
  • ssl_sslv2=NO et ssl_sslv3=NO : désactive SSLv2 et SSLv3 (obsolètes et non sécurisés)
rsa_cert_file=/etc/nginx/ssl/inception.crt
rsa_private_key_file=/etc/nginx/ssl/inception.key

Certificats SSL : on réutilise les mêmes certificats que nginx (via le volume certs).

require_ssl_reuse=NO
ssl_ciphers=HIGH
  • require_ssl_reuse=NO : ne pas réutiliser la session SSL (évite des bugs avec certains clients)
  • ssl_ciphers=HIGH : utilise uniquement des ciphers forts

Script de setup (setup.sh)

#!/bin/sh
FTP_PASS=$(cat /run/secrets/ftp_password)

Lecture du mot de passe FTP depuis le secret.

if [ -z "$FTP_USER" ] || [ -z "$FTP_PASS" ]; then
    echo "ERROR: FTP_USER or FTP_PASS not set"
    exit 1
fi

Vérification des variables.

  • [ -z "$VAR" ] : vrai si la variable est vide
  • exit 1 : quitte avec un code d'erreur
echo "Nettoyage manuel de l'UID 82..."
sed -i "/:x:82:/d" /etc/passwd
sed -i "/:x:82:/d" /etc/group
sed -i "/^$FTP_USER:/d" /etc/passwd
sed -i "/^$FTP_USER:/d" /etc/group

Nettoyage des conflits d'UID.

Alpine crée parfois un utilisateur avec l'UID 82. On le supprime manuellement pour éviter les conflits.

  • sed -i "/:x:82:/d" : supprime toutes les lignes contenant :x:82: (l'UID 82)
  • sed -i "/^$FTP_USER:/d" : supprime l'utilisateur s'il existe déjà
echo "Création de l'utilisateur $FTP_USER..."
adduser -D -u 82 -h /var/www/html -s /bin/sh "$FTP_USER"

Création de l'utilisateur FTP.

  • -D : pas de mot de passe (on le définira après avec chpasswd)
  • -u 82 : UID 82 (pour correspondre à www-data)
  • -h /var/www/html : dossier home
  • -s /bin/sh : shell par défaut
echo "$FTP_USER:$FTP_PASS" | chpasswd

Définit le mot de passe.

chpasswd lit l'entrée standard au format user:password.

mkdir -p /var/run/vsftpd/empty
chmod 555 /var/run/vsftpd/empty

Dossier requis par vsftpd pour le mode seccomp_sandbox.

chown root:root /var/www/html
chmod 755 /var/www/html
chown -R 82:82 /var/www/html 2>/dev/null

Permissions :

  • Le dossier racine doit appartenir à root (sécurité)
  • Les sous-dossiers appartiennent à l'utilisateur FTP (UID 82)
echo "Starting FTPS for $FTP_USER..."
exec /usr/sbin/vsftpd /etc/vsftpd.conf

Lance vsftpd en foreground.


Adminer

Adminer, c'est une interface web pour gérer MariaDB (comme phpMyAdmin, mais en un seul fichier PHP).

Dockerfile

FROM alpine:3.22
RUN apk update && apk upgrade && \
	apk add --no-cache php84 php84-mysqli php84-session php84-iconv wget

Paquets installés :

  • php84 : PHP
  • php84-mysqli : pour se connecter à MariaDB
  • php84-session : gestion des sessions (requis par Adminer)
  • php84-iconv : conversion d'encodage (requis par Adminer)
  • wget : pour télécharger Adminer
RUN ln -s /usr/bin/php84 /usr/bin/php
WORKDIR /var/www/html
RUN wget https://www.adminer.org/latest.php -O index.php

Téléchargement d'Adminer.

Adminer est un seul fichier PHP. On le télécharge et on le renomme index.php.

EXPOSE 8080
CMD ["php", "-S", "0.0.0.0:8080", "index.php"]

Serveur web PHP intégré.

  • php -S : lance le serveur web intégré de PHP
  • 0.0.0.0:8080 : écoute sur toutes les interfaces, port 8080
  • index.php : fichier à servir

Pourquoi ne pas utiliser nginx ? Pour Adminer, c'est overkill. Le serveur intégré de PHP suffit largement.


Site Statique

Un site statique tout simple pour montrer que tu maîtrises nginx.

Dockerfile

FROM alpine:3.22
RUN apk update && apk upgrade && \
	apk add --no-cache nginx
RUN mkdir -p /var/www/static /run/nginx
  • /var/www/static : où stocker le HTML
  • /run/nginx : pour le PID de nginx
COPY conf/default.conf /etc/nginx/http.d/default.conf
COPY conf/index.html /var/www/static/index.html
EXPOSE 8081

Port personnalisé (pour différencier du nginx principal).

CMD ["nginx", "-g", "daemon off;"]

Configuration Nginx (default.conf)

server {
	listen 8081;
	server_name localhost;
	root /var/www/static;
	index index.html;
	location / {
		try_files $uri $uri/ =404;
	}
}

Config ultra-simple.

  • Écoute sur le port 8081
  • Sert les fichiers depuis /var/www/static
  • Si le fichier n'existe pas, retourne 404

Page HTML (index.html)

<!DOCTYPE html>
<html>
<head>
    <title>Site Statique - Inception</title>
    <style>
        body { background: #232323; color: #00ff00; font-family: monospace; text-align: center; padding: 50px; }
        h1 { border: 2px solid #00ff00; display: inline-block; padding: 10px; }
    </style>
</head>
<body>
    <h1>SYSTEM STATUS: ONLINE</h1>
    <p>Ceci est le site statique bonus de namichel.</p>
</body>
</html>

Style hacker/matrix : fond noir, texte vert, monospace. Ça fait son petit effet ! 😎


Portainer

Portainer, c'est une UI web pour gérer Docker (voir les conteneurs, images, volumes, etc.).

Dockerfile

FROM alpine:3.22
RUN apk update && apk upgrade && \
	apk add --no-cache libc6-compat gcompat ca-certificates curl

Compatibilité avec les binaires Linux.

Portainer est compilé pour glibc, mais Alpine utilise musl. On installe des libs de compatibilité :

  • libc6-compat : compatibilité libc6
  • gcompat : compatibilité glibc
  • ca-certificates : certificats SSL (pour HTTPS)
  • curl : pour télécharger Portainer
RUN curl -L https://github.com/portainer/portainer/releases/download/2.21.5/portainer-2.21.5-linux-amd64.tar.gz -o portainer.tar.gz && \
	tar -xvf portainer.tar.gz && \
	rm portainer.tar.gz && \
	mv portainer /usr/local/bin/portainer_dir

Téléchargement et extraction de Portainer.

  1. curl -L : télécharge Portainer (le -L suit les redirections)
  2. tar -xvf : extrait l'archive
  3. rm portainer.tar.gz : supprime l'archive
  4. mv portainer /usr/local/bin/portainer_dir : déplace le dossier
WORKDIR /usr/local/bin/portainer_dir
EXPOSE 9443

Portainer écoute sur le port 9443 (HTTPS).

COPY conf/setup.sh /usr/local/bin/setup.sh
RUN chmod +x /usr/local/bin/setup.sh
ENTRYPOINT ["/usr/local/bin/setup.sh"]

Script de setup (setup.sh)

#!/bin/sh
if [ ! -f /run/secrets/portainer_password ]; then
	echo "Portainer: Password secret not found!"
else
	echo "Portainer: Password secret OK!"
fi

Vérification du secret.

Simple check pour debug.

mkdir -p /data

Crée le dossier de données de Portainer.

exec /usr/local/bin/portainer_dir/portainer \
	--data /data \
	--assets /usr/local/bin/portainer_dir/ \
	--admin-password-file /run/secrets/portainer_password \
	-H unix:///var/run/docker.sock

Lancement de Portainer.

  • --data /data : où stocker les données
  • --assets : où trouver les assets web (CSS, JS, etc.)
  • --admin-password-file : fichier contenant le mot de passe admin
  • -H unix:///var/run/docker.sock : se connecte au socket Docker de l'hôte

Le mot de passe Portainer :

⚠️ IMPORTANT : Le mot de passe doit faire au moins 12 caractères. Sinon, Portainer refuse de démarrer.

Dans le Makefile, j'ai mis pass123456789 (13 caractères). Change-le pour un vrai mot de passe en correction.


🔐 Gestion des Secrets

Pourquoi des secrets ?

Les secrets Docker permettent de stocker des données sensibles (mots de passe) de manière sécurisée.

Avantages :

  • Pas de mots de passe dans les variables d'environnement (visibles avec docker inspect)
  • Pas de mots de passe hardcodés dans les Dockerfiles
  • Fichiers montés en read-only dans /run/secrets/

Comment ça marche ?

  1. Dans le docker-compose.yml :
secrets:
  db_password:
    file: ../secrets/db_password.txt
  1. Docker monte le fichier dans /run/secrets/db_password du conteneur.

  2. Dans le conteneur, on lit le secret :

DB_PASS=$(cat /run/secrets/db_password)

Les secrets de mon projet

  • db_password : mot de passe de l'utilisateur WordPress sur MariaDB
  • db_root_password : mot de passe root de MariaDB
  • wp_admin_password : mot de passe de l'admin WordPress
  • wp_user_password : mot de passe du deuxième utilisateur WordPress
  • ftp_password : mot de passe FTP
  • portainer_password : mot de passe admin Portainer (min 12 caractères)

Comment créer de vrais secrets ?

# Exemple avec un mot de passe fort
openssl rand -base64 32 > secrets/db_password.txt

# Ou manuellement
echo "MonMotDePasseComplexe123!" > secrets/wp_admin_password.txt

⚠️ GITIGNORE : n'oublie JAMAIS de mettre le dossier secrets/ dans ton .gitignore !

secrets/
.env

💡 Conseils et Pièges à Éviter

1. Le fichier .env

Tu dois créer un fichier .env à la racine de ton projet avec toutes les variables d'environnement.

Exemple de .env :

# Domain
DOMAIN_NAME=namichel.42.fr

# MariaDB
SQL_DATABASE=wordpress
SQL_USER=wpuser

# WordPress
WP_ADMIN_USER=admin
WP_ADMIN_EMAIL=admin@inception.fr
WP_USER=author
WP_USER_EMAIL=author@inception.fr

# PHP
PHP_USER=www-data

# FTP
FTP_USER=ftpuser

⚠️ N'oublie pas de mettre .env dans le .gitignore !

2. Le fichier /etc/hosts

Pour que namichel.42.fr fonctionne en local, ajoute-le dans /etc/hosts :

sudo nano /etc/hosts

Ajoute la ligne :

127.0.0.1 namichel.42.fr

Sauvegarde et ferme. Maintenant tu peux accéder à ton site via https://namichel.42.fr.

3. Le certificat SSL auto-signé

Ton navigateur va te crier dessus : "Connexion non sécurisée !". C'est NORMAL.

Le certificat est auto-signé, donc pas reconnu par les autorités de certification. En correction, tu devras expliquer ça.

Pour contourner l'avertissement :

  • Chrome/Brave : clique sur "Avancé" → "Continuer vers le site"
  • Firefox : "Avancé" → "Accepter le risque et continuer"

4. Les permissions sur les volumes

Si tu as des problèmes de permissions (WordPress ne peut pas écrire, FTP ne peut pas uploader), vérifie les UID/GID.

Vérifier les permissions :

ls -la /home/namichel/data/wordpress

Si c'est cassé, reset les permissions :

sudo chown -R 82:82 /home/namichel/data/wordpress

L'UID 82 = www-data (utilisé par nginx et PHP-FPM).

5. Debug d'un conteneur

Voir les logs :

docker logs mariadb
docker logs wordpress
docker logs nginx

Entrer dans un conteneur :

docker exec -it mariadb sh
docker exec -it wordpress sh

Vérifier la connexion MariaDB depuis WordPress :

docker exec -it wordpress sh
mariadb -hmariadb -uwpuser -p
# Entre le mot de passe
SHOW DATABASES;

Vérifier que nginx voit les fichiers WordPress :

docker exec -it nginx sh
ls -la /var/www/html

6. Le piège de depends_on

depends_on garantit l'ordre de démarrage, mais PAS que le service soit prêt.

Exemple : MariaDB peut démarrer, mais mettre 5 secondes à accepter les connexions. Si WordPress se lance trop tôt, il crash.

Solution : les boucles until dans les scripts de setup.

until mariadb-admin ping -h"mariadb" --silent; do
    echo "Waiting for MariaDB..."
    sleep 2
done

7. Redis : vérifier que ça marche

Voir les stats Redis :

docker exec -it redis sh
redis-cli
INFO stats

Tu verras le nombre de commandes exécutées, les hits/misses du cache, etc.

Dans WordPress : va dans le dashboard, cherche "Redis" dans les plugins. Tu devrais voir le plugin "Redis Object Cache" activé avec un statut "Connected".

8. Tester le FTP

Avec FileZilla :

  • Host : namichel.42.fr
  • Port : 21
  • Protocol : FTP - Protocole de transfert de fichiers
  • Encryption : Require explicit FTP over TLS
  • User : la valeur de FTP_USER dans ton .env
  • Password : le contenu de secrets/ftp_password.txt

Tu devrais voir les fichiers WordPress et pouvoir uploader/modifier.

9. Portainer : premier lancement

Lors du premier accès à Portainer (https://namichel.42.fr/portainer/), tu devras :

  1. Créer un compte admin (le mot de passe que tu as mis dans le secret)
  2. Sélectionner "Docker" comme environnement
  3. Connect to the local Docker socket

Ensuite, tu verras tous tes conteneurs, images, volumes, etc. C'est très pratique pour le debug !

10. Les erreurs courantes

"Error establishing a database connection" (WordPress) :

  • MariaDB n'est pas prête → vérifie les logs avec docker logs mariadb
  • Mauvais mot de passe → vérifie que le secret est bien monté
  • Mauvais nom d'hôte → dans wp-config.php, ça doit être mariadb:3306, pas localhost

"502 Bad Gateway" (nginx) :

  • PHP-FPM est down → docker logs wordpress
  • PHP-FPM n'écoute pas sur le bon port → vérifie www.conf (doit être listen = 9000)

Portainer ne démarre pas :

  • Mot de passe trop court → doit faire au moins 12 caractères
  • Socket Docker non monté → vérifie le volume /var/run/docker.sock dans le docker-compose

Portainer : "Cannot connect to the Docker daemon" (Docker Engine 29.0.0+) :

  • PROBLÈME CONNU : Docker Engine 29.0.0 a introduit des breaking changes incompatibles avec Portainer

  • Solution temporaire : forcer une version d'API Docker plus ancienne

    # Éditer la configuration systemd de Docker
    sudo systemctl edit docker.service
    
    # Ajouter ces lignes AVANT "### Lines below this comment will be discarded"
    [Service]
    Environment=DOCKER_MIN_API_VERSION=1.24
    
    # Sauvegarder et quitter (Ctrl+X, puis Y, puis Enter)
    
    # Redémarrer Docker
    sudo systemctl restart docker
    
    # Relancer tes conteneurs
    make re
  • Alternative : spécifier la version d'API directement dans le docker-compose (comme je l'ai fait)

    portainer:
      environment:
        - DOCKER_API_VERSION=1.43
  • Vérifier ta version Docker :

    docker --version

    Si tu as Docker 29.0.0 ou plus récent, ce problème te concerne.

  • Solution long terme : attendre une mise à jour de Portainer compatible avec Docker 29.0.0+

FTP : "Connection refused" :

  • Le port 21 n'est pas exposé → vérifie le ports dans le docker-compose
  • vsftpd a crashé → docker logs ftp

11. Les commandes utiles

Rebuild tout et relancer :

make re

Voir les conteneurs actifs :

docker ps

Voir tous les conteneurs (même arrêtés) :

docker ps -a

Voir les volumes :

docker volume ls

Voir les réseaux :

docker network ls

Voir les images :

docker images

Supprimer une image en force :

docker rmi -f <image_id>

Voir l'utilisation disque de Docker :

docker system df

Nettoyer tout ce qui est inutilisé :

docker system prune -a --volumes

⚠️ DANGER : cette commande supprime TOUT (images, volumes, réseaux inutilisés). À utiliser avec précaution !

12. La checklist de correction

Avant ta correction, vérifie que :

✅ Tous les conteneurs démarrent correctement (docker ps) ✅ WordPress est accessible sur https://namichel.42.fr ✅ Tu peux te connecter avec l'admin et l'utilisateur normal ✅ Adminer est accessible sur https://namichel.42.fr/adminer ✅ Le site statique est accessible sur https://namichel.42.fr/static/ ✅ Portainer est accessible sur https://namichel.42.fr/portainer/ ✅ Tu peux te connecter en FTP avec FileZilla ✅ Redis est connecté (vérifie dans le dashboard WordPress) ✅ Tous tes Dockerfiles commencent par FROM alpine:3.22 ou une version d'Alpine ✅ Aucune image ne vient de Docker Hub (sauf Alpine) ✅ Les volumes persistent sur /home/login/data ✅ Les secrets ne sont pas visibles avec docker inspect ✅ Ton .env et secrets/ sont dans le .gitignore ✅ Tu n'as pas de mot de passe en clair dans les Dockerfiles ou docker-compose

13. Questions fréquentes en correction

"Pourquoi Alpine ?" → Légère (5 Mo), sécurisée, parfaite pour Docker. Les images sont plus petites et démarrent plus vite.

"Pourquoi l'UID 82 partout ?" → Sur Alpine, www-data a l'UID 82 (contrairement à Debian où c'est le 33). J'ai aligné tous mes services (PHP, nginx, FTP) sur cet UID pour éviter les conflits de permissions sur le volume partagé /var/www/html. C'est LE défi technique du projet : trois services doivent pouvoir lire/écrire les mêmes fichiers. La solution : un seul UID pour tout le monde.

"Pourquoi tu supprimes l'utilisateur par défaut dans le FTP ?" → Alpine crée automatiquement un utilisateur avec l'UID 82. Pour créer mon propre utilisateur FTP avec l'UID 82 (pour matcher PHP et nginx), je dois d'abord "libérer" cet UID en supprimant l'utilisateur par défaut. C'est un peu brutal, mais c'est la seule solution pour garantir que FTP, PHP et nginx utilisent tous le même UID.

"C'est quoi la différence entre CMD et ENTRYPOINT ?"ENTRYPOINT = commande principale (toujours exécutée), CMD = arguments par défaut (peuvent être overridés).

"Pourquoi daemon off pour nginx ?" → Docker a besoin d'un processus en foreground. Si nginx se lance en daemon, Docker pense que le conteneur est fini.

"C'est quoi FastCGI ?" → Un protocole pour exécuter du code dynamique (PHP) via un serveur web (nginx). PHP-FPM écoute sur un port, nginx lui transmet les requêtes PHP.

"Pourquoi bind mount et pas volume Docker ?" → Le sujet impose que les volumes soient sur /home/login/data. Avec un volume Docker classique, on n'a pas le contrôle de l'emplacement.

"Comment nginx communique avec WordPress ?" → Via le réseau Docker inception. nginx envoie les requêtes PHP à wordpress:9000 (FastCGI). Pour les fichiers statiques, nginx les lit directement sur le volume partagé.

"Pourquoi Redis ?" → Cache en mémoire. WordPress stocke des données fréquemment utilisées (posts, pages, widgets) dans Redis au lieu de requêter MariaDB à chaque fois. Ça accélère le site.

"C'est quoi un secret Docker ?" → Un mécanisme pour stocker des données sensibles. Docker monte les secrets en read-only dans /run/secrets/. C'est plus sécurisé que les variables d'environnement.

"Pourquoi TLS 1.2 ou 1.3 ?" → Ce sont les versions sécurisées de TLS. Les versions antérieures (TLS 1.0, 1.1, SSL 2, SSL 3) sont obsolètes et vulnérables.


🎓 Conclusion

Voilà, tu as maintenant une vue complète de mon projet Inception. J'ai tout expliqué ligne par ligne, comme si on codait ensemble.

Les points clés à retenir :

  1. Architecture : nginx (reverse proxy) → WordPress (PHP-FPM) → MariaDB
  2. Alpine : légère et parfaite pour Docker
  3. Secrets : pour sécuriser les mots de passe
  4. Volumes bind : pour persister les données sur /home/login/data
  5. Network bridge : pour que les conteneurs communiquent entre eux
  6. Scripts de setup : pour initialiser les services au premier démarrage
  7. Boucles until : pour attendre que les services dépendants soient prêts

Les bonus :

  • Redis : cache WordPress
  • FTP : uploader des fichiers
  • Adminer : gérer la base de données
  • Site statique : montrer la maîtrise de nginx
  • Portainer : UI Docker

Mes conseils :

  • Test, test, test. Lance make re souvent pendant le dev.
  • Utilise docker logs et docker exec pour debugger.
  • N'hésite pas à lire les docs officielles (nginx, PHP-FPM, MariaDB, etc.).
  • En correction, sois clair et précis. Explique tes choix techniques.

Si tu as des questions ou des problèmes, n'hésite pas à relire ce tuto ou à checker les docs officielles. Bon courage pour ta correction, et que la force de Docker soit avec toi ! 🐳🚀


📚 Ressources Utiles


Fait avec ❤️ par un étudiant de 42 pour les étudiants de 42.

N'oublie pas : le meilleur moyen d'apprendre, c'est de casser (et réparer) ton projet 1000 fois. make fclean && make est ton meilleur ami ! 😄

About

Inception (CARE: tutorial)

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors