Back to Blog
Inception — The Complete Guide
March 8, 202612 min read
Tech & Projects

Inception — The Complete Guide

A practical, beginner-friendly guide to the 42 school Inception project. Whether you're just starting out or stuck mid-way, this guide walks you through every concept, every phase, and every common error — step by step.

excalidraw photo explain the inception infrastructure
An Image show the request flow - browser to databse - inception - 42 network - 1337
tutorial
docker
inception
42school
devops
containers
wordpress
php
php-fpm
linux
beginner-guide
infrastructure
tls
docker-compose

Table of Contents

  1. What is Inception?
  2. Pre-Knowledge — Concepts You Must Understand First
  3. Phase 0 — Do Everything Manually First
  4. Phase 1 — Your First Container (MariaDB)
  5. Phase 2 — Add WordPress + PHP-FPM
  6. Phase 3 — Add NGINX + TLS
  7. Phase 4 — Wire Everything with Docker Compose
  8. Phase 5 — Make It Subject Compliant
  9. Common Errors & Fixes
  10. Final Checklist
  11. Useful Links

1. What is Inception?

Inception is a 42 school project where you build a small but complete web infrastructure entirely using Docker containers, running inside a Virtual Machine.

The goal is to understand how modern web applications are deployed using containerization. You will create and wire together three services:

  • NGINX — the web server and reverse proxy (the only door to the outside world)
  • WordPress + PHP-FPM — the web application
  • MariaDB — the database that stores all WordPress content

Each service runs in its own isolated container. They communicate through a private Docker network. Everything is defined in a single docker-compose.yml file and starts with one make command.

VM (VirtualBox)
└── Docker Network: inception
├── NGINX container ← only exposed to internet (port 443)
├── WordPress container ← talks to both NGINX and MariaDB
└── MariaDB container ← only reachable from WordPress

Think of it as building your own mini data center, from scratch, on your machine.

2. Pre-Knowledge

Before writing a single line of code, you need to understand these concepts. Don't skip this — it will save you hours of confusion.

Virtual Machines vs Containers

A Virtual Machine (VM) emulates an entire computer, including its own OS. It runs on a hypervisor like VirtualBox. VMs are heavy — gigabytes of disk space and RAM just for the OS.

A container shares the host machine's OS kernel but isolates the application in its own filesystem and network. It only packages the application and its dependencies. Containers are:

  • Lightweight (megabytes, not gigabytes)
  • Fast to start (seconds, not minutes)
  • Reproducible across any machine
In Inception: your VM is the house. Your containers are the rooms inside it.

Docker Core Concepts

Image — a read-only blueprint for a container. Built from a Dockerfile. Layered: each instruction adds a layer on top of the last.

Container — a running instance of an image. When you start a container, Docker adds a thin writable layer on top of the image, sets up an isolated network and filesystem, and runs your command.

Dockerfile — a text file with instructions to build an image:

FROM debian:bullseye
RUN apt-get update && apt-get install -y nginx
COPY conf/nginx.conf /etc/nginx/nginx.conf
EXPOSE 443
CMD ["nginx", "-g", "daemon off;"]

Docker Compose — a tool to define and run multi-container apps from a single docker-compose.yml file. Replaces running many docker run commands manually.

Network — connects containers so they can talk to each other by service name. When two containers are on the same bridge network, Docker acts as a mini DNS: the container named mariadb is reachable at the hostname mariadb.

Volume — persistent storage that survives container restarts. Without a volume, all data is lost when a container is removed.

Key Docker Commands

Command

What it does

docker build -t myimage .

Build an image from Dockerfile

docker run -it myimage

Run a container interactively

docker run -d myimage

Run in background (detached)

docker ps

List running containers

docker exec -it <id> bash

Open a shell inside a container

docker logs <id>

View container logs

docker stop <id>

Stop a container

docker rm <id>

Remove a stopped container

docker volume ls

List all volumes

docker network inspect <name>

Inspect a network

Why service mysql start Fails in Docker

Inside a Docker container there is no systemd. Commands like service mysql start or systemctl start mysql rely on systemd to manage processes. Containers are too minimal to run systemd by default.

So you start services directly using their binary:

mysqld_safe & # starts MariaDB directly
php-fpm7.4 -F # starts PHP-FPM directly in foreground
nginx -g "daemon off;" # starts NGINX in foreground

The & runs the process in the background. wait at the end keeps the script alive and monitors the background process — if MariaDB crashes, the container stops too (correct behavior). Using tail -f /dev/null instead keeps the container alive even if the service crashes, which hides problems.

The Security Model (Why Only NGINX Has an Exposed Port)

Internet → can only reach → NGINX (port 443)
NGINX → can only reach → WordPress (port 9000, internal network)
WordPress→ can only reach → MariaDB (port 3306, internal network)
MariaDB → not reachable from anywhere except WordPress

This is a basic security principle: minimize your attack surface. MariaDB uses 'wpuser'@'%' (allow from any host) because from its perspective, WordPress is a remote connection coming from another container IP — not localhost. But since port 3306 is never exposed outside the Docker network, @'%' is safe in practice.

NGINX, PHP-FPM, and FastCGI

NGINX cannot execute PHP on its own. When it receives a request for a .php file, it forwards it to PHP-FPM using the FastCGI protocol on port 9000. PHP-FPM runs the script and returns the HTML output to NGINX.

location ~ \.php$ {
fastcgi_pass wordpress:9000; # forward to WordPress container
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}

For static files (CSS, JS, images), NGINX serves them directly from the shared volume — no PHP needed.

WP_HOME vs WP_SITEURL

Both tell WordPress where it lives, but they serve different purposes:

  • WP_SITEURL — where WordPress core files are (the PHP code, wp-admin)
  • WP_HOME — where your website is accessible to visitors

If these are not set correctly, WordPress redirects every request to the wrong URL. Always set both in wp-config.php or via WP-CLI:

wp config set WP_HOME "https://${DOMAIN_NAME}" --path=/var/www/html --allow-root
wp config set WP_SITEURL "https://${DOMAIN_NAME}" --path=/var/www/html --allow-root

3. Phase 0 — Do Everything Manually First

Goal: Install and configure all three services by hand on a Debian VM. Once the WordPress site works manually, you'll know exactly what your Dockerfiles need to do.

Step 1 — Prepare the VM

Install a fresh Debian 11 (Bullseye) VM in VirtualBox. Download the amd64 netinst ISO from:

https://www.debian.org/distrib/netinst

Then update the system:

sudo apt update && sudo apt upgrade -y
sudo apt install -y curl wget vim openssl

Step 2 — Install & Configure MariaDB

sudo apt install -y mariadb-server
sudo systemctl start mariadb
sudo mysql_secure_installation

# Connect as root and set up the database
sudo mysql -u root

Inside the MySQL shell:

CREATE DATABASE wordpress;
CREATE USER 'wpuser'@'localhost' IDENTIFIED BY 'wppassword';
GRANT ALL PRIVILEGES ON wordpress.* TO 'wpuser'@'localhost';
FLUSH PRIVILEGES;
EXIT;

Verify the user works:

mysql -u wpuser -pwppassword wordpress

Step 3 — Install & Configure PHP-FPM

sudo apt install -y php-fpm php-mysql
sudo systemctl start php-fpm
sudo systemctl status php-fpm

Step 4 — Install WordPress

cd /var/www/html
sudo wget https://wordpress.org/latest.tar.gz
sudo tar -xzf latest.tar.gz
sudo mv wordpress/* .
sudo chown -R www-data:www-data /var/www/html
sudo cp wp-config-sample.php wp-config.php

Edit wp-config.php and fill in:

define( 'DB_NAME', 'wordpress' );
define( 'DB_USER', 'wpuser' );
define( 'DB_PASSWORD', 'wppassword' );
define( 'DB_HOST', 'localhost' );

Step 5 — Install & Configure NGINX

sudo apt install -y nginx

Create /etc/nginx/sites-available/wordpress:

server {
listen 80;
server_name localhost;
root /var/www/html;
index index.php;

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

location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php-fpm.sock;
}
}

Enable the site:

sudo ln -s /etc/nginx/sites-available/wordpress /etc/nginx/sites-enabled/
sudo rm /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginx

Step 6 — Test HTTP First

Open a browser and go to http://localhost. You should see the WordPress setup page. Complete the installation, then verify you can log in at http://localhost/wp-admin.

Step 7 — Add TLS (HTTPS)

sudo mkdir -p /etc/nginx/ssl
sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout /etc/nginx/ssl/key.pem \
-out /etc/nginx/ssl/cert.pem \
-subj "/CN=localhost"

Update NGINX to use port 443 and TLS only:

server {
listen 443 ssl;
server_name localhost;
root /var/www/html;
index index.php;

ssl_certificate /etc/nginx/ssl/cert.pem;
ssl_certificate_key /etc/nginx/ssl/key.pem;
ssl_protocols TLSv1.2 TLSv1.3;

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

location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php-fpm.sock;
}
}

Reload NGINX, visit https://localhost, accept the browser warning, and confirm WordPress loads.

Step 8 — Verify Everything

sudo systemctl status mariadb # active
sudo systemctl status php-fpm # active
sudo systemctl status nginx # active

Visit https://localhost/wp-admin and log in. Create a test post to confirm the database is working.

Once all of this works manually — you're ready to containerize it.

4. Phase 1 — Your First Container (MariaDB)

Goal: Get MariaDB running in a Docker container, with the database and user created automatically on startup.

Folder Structure

inception/
└── srcs/
└── requirements/
└── mariadb/
├── Dockerfile
└── tools/
└── init.sh

Install Docker

sudo apt install -y docker.io
sudo systemctl start docker

Dockerfile

FROM debian:bullseye

RUN apt-get update && apt-get install -y mariadb-server \
&& rm -rf /var/lib/apt/lists/*

COPY tools/init.sh /init.sh
RUN chmod +x /init.sh

CMD ["/init.sh"]

init.sh

#!/bin/bash
mysqld_safe &
sleep 3

mysql -u root <<EOF
CREATE DATABASE IF NOT EXISTS wordpress;
CREATE USER IF NOT EXISTS 'wpuser'@'%' IDENTIFIED BY 'wppassword';
GRANT ALL PRIVILEGES ON wordpress.* TO 'wpuser'@'%';
FLUSH PRIVILEGES;
EOF

wait

Why mysqld_safe & and not service mysql start? Containers have no systemd. You must start the binary directly. The & runs it in the background so the script can continue running setup commands. wait then keeps the script alive watching the background process.
Why @'%' and not @'localhost'? In Docker, WordPress runs in a separate container. From MariaDB's view, that's a remote connection — not localhost. @'%' allows connections from any host, which is safe because port 3306 is never exposed outside the Docker network.

Build and Run

cd srcs/requirements/mariadb
docker build -t mariadb_test .
docker run -it mariadb_test

Verify

Open a second terminal:

docker exec -it <container_id> mysql -u wpuser -pwppassword wordpress

If you see a MySQL prompt — Phase 1 done.

5. Phase 2 — Add WordPress + PHP-FPM

Goal: Get WordPress running in its own container, connected to MariaDB over a Docker network.

Create the Docker Network

docker network create inception

Re-run MariaDB on the network:

docker stop <old_mariadb> && docker rm <old_mariadb>
docker run -d --name mariadb --network inception mariadb_test

WordPress Dockerfile

FROM debian:bullseye

RUN apt-get update && apt-get install -y \
php-fpm \
php-mysql \
wget \
curl \
&& rm -rf /var/lib/apt/lists/*

COPY tools/init.sh /init.sh
RUN chmod +x /init.sh

EXPOSE 9000
CMD ["/init.sh"]

WordPress init.sh

#!/bin/bash

# Download WordPress
wget -q https://wordpress.org/latest.tar.gz -P /tmp
tar -xzf /tmp/latest.tar.gz -C /tmp
mkdir -p /var/www/html
mv /tmp/wordpress/* /var/www/html/

# Download WP-CLI
curl -O 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

# Wait for MariaDB to be ready
sleep 5

# Create wp-config.php
wp config create \
--dbname=wordpress \
--dbuser=wpuser \
--dbpass=wppassword \
--dbhost=mariadb \
--path=/var/www/html \
--allow-root

# Install WordPress
wp core install \
--url=localhost \
--title="Inception" \
--admin_user=myadmin \
--admin_password=adminpass \
--admin_email=admin@example.com \
--path=/var/www/html \
--allow-root

# Create editor user
wp user create editor editor@example.com \
--role=editor \
--user_pass=editorpass \
--path=/var/www/html \
--allow-root

# Start PHP-FPM
mkdir -p /run/php
exec php-fpm7.4 -F

Build and Run

cd srcs/requirements/wordpress
docker build -t wordpress_test .
docker run -d --name wordpress --network inception wordpress_test

Verify

docker exec -it wordpress php -r "
\$c = new mysqli('mariadb', 'wpuser', 'wppassword', 'wordpress');
echo \$c->connect_error ? 'FAILED' : 'SUCCESS';
"

If you see SUCCESSPhase 2 done.

6. Phase 3 — Add NGINX + TLS

Goal: NGINX in its own container with TLS, wired to WordPress. Visit https://localhost and see the WordPress site.

Folder Structure

nginx/
├── Dockerfile
├── conf/
│ └── nginx.conf
└── tools/
└── init.sh

NGINX Dockerfile

FROM debian:bullseye

RUN apt-get update && apt-get install -y \
nginx \
openssl \
&& rm -rf /var/lib/apt/lists/*

COPY conf/nginx.conf /etc/nginx/sites-available/default
COPY tools/init.sh /init.sh
RUN chmod +x /init.sh

EXPOSE 443
CMD ["/init.sh"]

nginx.conf

server {
listen 443 ssl;
server_name localhost;

ssl_certificate /etc/nginx/ssl/cert.pem;
ssl_certificate_key /etc/nginx/ssl/key.pem;
ssl_protocols TLSv1.2 TLSv1.3;

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

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

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

NGINX init.sh

#!/bin/bash

mkdir -p /etc/nginx/ssl
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout /etc/nginx/ssl/key.pem \
-out /etc/nginx/ssl/cert.pem \
-subj "/CN=localhost"

exec nginx -g "daemon off;"

Build, Run, and Share a Volume

NGINX needs access to WordPress files to serve static assets. Create a shared volume:

docker volume create wp_files

# Restart WordPress with volume
docker stop wordpress && docker rm wordpress
docker run -d --name wordpress --network inception \
-v wp_files:/var/www/html wordpress_test

# Run NGINX with same volume
docker build -t nginx_test .
docker run -d --name nginx --network inception \
-p 443:443 \
-v wp_files:/var/www/html nginx_test

Visit https://localhost in your VM browser, accept the certificate warning — Phase 3 done.

7. Phase 4 — Wire Everything with Docker Compose

Goal: Replace all manual docker run commands with a single make command using Docker Compose.

Project Structure

inception/
├── Makefile
└── srcs/
├── .env
├── docker-compose.yml
└── requirements/
├── mariadb/
│ ├── Dockerfile
│ └── tools/init.sh
├── wordpress/
│ ├── Dockerfile
│ └── tools/init.sh
└── nginx/
├── Dockerfile
├── conf/nginx.conf
└── tools/init.sh

.env File

DOMAIN_NAME=localhost

MYSQL_DATABASE=wordpress
MYSQL_USER=wpuser
MYSQL_PASSWORD=wppassword
MYSQL_ROOT_PASSWORD=rootpassword

WP_TITLE=Inception
WP_ADMIN_USER=myadmin
WP_ADMIN_PASSWORD=adminpass
WP_ADMIN_EMAIL=admin@example.com
WP_USER=editor
WP_USER_PASSWORD=editorpass
WP_USER_EMAIL=editor@example.com

Never commit .env to git. Add it to .gitignore.

docker-compose.yml

version: '3'

services:

mariadb:
build: ./requirements/mariadb
container_name: mariadb
networks:
- inception
volumes:
- db_data:/var/lib/mysql
env_file:
- .env
restart: always

wordpress:
build: ./requirements/wordpress
container_name: wordpress
networks:
- inception
volumes:
- wp_files:/var/www/html
env_file:
- .env
depends_on:
- mariadb
restart: always

nginx:
build: ./requirements/nginx
container_name: nginx
networks:
- inception
volumes:
- wp_files:/var/www/html
ports:
- "443:443"
depends_on:
- wordpress
restart: always

networks:
inception:
driver: bridge

volumes:
db_data:
driver: local
wp_files:
driver: local

Update init.sh Files to Use Environment Variables

MariaDB init.sh:

#!/bin/bash
mysqld_safe &

until mysqladmin ping --silent; do
sleep 1
done

if ! mysql -u root -e "USE ${MYSQL_DATABASE};" 2>/dev/null; then
mysql -u root <<EOF
CREATE DATABASE IF NOT EXISTS ${MYSQL_DATABASE};
CREATE USER IF NOT EXISTS '${MYSQL_USER}'@'%' IDENTIFIED BY '${MYSQL_PASSWORD}';
GRANT ALL PRIVILEGES ON ${MYSQL_DATABASE}.* TO '${MYSQL_USER}'@'%';
ALTER USER 'root'@'localhost' IDENTIFIED BY '${MYSQL_ROOT_PASSWORD}';
FLUSH PRIVILEGES;
EOF
fi

mysqladmin -u root -p${MYSQL_ROOT_PASSWORD} shutdown
exec mysqld_safe

The if block only runs setup once — on first start when the database doesn't exist. On subsequent restarts it skips straight to exec mysqld_safe. This prevents the "Access denied for root" crash loop.

WordPress init.sh:

#!/bin/bash

until mysqladmin ping \
-h mariadb -u ${MYSQL_USER} -p${MYSQL_PASSWORD} --silent 2>/dev/null; do
echo "Waiting for MariaDB..."
sleep 1
done

if [ ! -f /var/www/html/wp-config.php ]; then
wp config create \
--dbname=${MYSQL_DATABASE} \
--dbuser=${MYSQL_USER} \
--dbpass=${MYSQL_PASSWORD} \
--dbhost=mariadb \
--path=/var/www/html \
--allow-root

wp config set WP_HOME "https://${DOMAIN_NAME}" --path=/var/www/html --allow-root
wp config set WP_SITEURL "https://${DOMAIN_NAME}" --path=/var/www/html --allow-root

wp core install \
--url=${DOMAIN_NAME} \
--title=${WP_TITLE} \
--admin_user=${WP_ADMIN_USER} \
--admin_password=${WP_ADMIN_PASSWORD} \
--admin_email=${WP_ADMIN_EMAIL} \
--path=/var/www/html \
--allow-root

wp user create ${WP_USER} ${WP_USER_EMAIL} \
--role=editor \
--user_pass=${WP_USER_PASSWORD} \
--path=/var/www/html \
--allow-root
fi

mkdir -p /run/php
exec php-fpm7.4 -F

Makefile

all:
docker-compose -f srcs/docker-compose.yml up --build

down:
docker-compose -f srcs/docker-compose.yml down

re: down all

clean: down
docker system prune -af

fclean: clean
docker volume rm $$(docker volume ls -q)

Run It

make

Visit https://localhostPhase 4 done.

8. Phase 5 — Make It Subject Compliant

Goal: Clean everything up to match exactly what the 42 subject requires.

1. Fix the Domain Name

Replace localhost with login.42.fr (use your 42 username):

echo "127.0.0.1 login.42.fr" | sudo tee -a /etc/hosts

Update .env:

DOMAIN_NAME=login.42.fr

2. Fix Volume Paths (Bind Mounts)

The subject requires volumes stored at /home/login/data/:

mkdir -p /home/login/data/wordpress
mkdir -p /home/login/data/mariadb

Update docker-compose.yml volumes:

volumes:
db_data:
driver: local
driver_opts:
type: none
o: bind
device: /home/login/data/mariadb
wp_files:
driver: local
driver_opts:
type: none
o: bind
device: /home/login/data/wordpress

3. NGINX — TLS Only

  • No port 80 listener
  • Only TLSv1.2 and TLSv1.3:

ssl_protocols TLSv1.2 TLSv1.3;

4. Container Rules

Every service in docker-compose.yml must have:

restart: always

No privileged: true, no --link, no network_mode: host.

5. WordPress Users

The subject requires:

  • One admin user — username must NOT contain admin or administrator
  • One regular user (editor, author, or subscriber role)

Check your .env — rename WP_ADMIN_USER if it contains "admin".

6. Containers Must Use exec to Run Services

Every container must end with exec so the service runs as PID 1:

exec php-fpm7.4 -F # wordpress
exec mysqld_safe # mariadb
exec nginx -g "daemon off;" # nginx

No infinite loops (while true; do sleep 1; done), no sleep infinity, no tail -f /dev/null as the main process.

7. Clean Up Dockerfiles

  • Start with FROM debian:bullseye — no pre-built service images
  • Add && rm -rf /var/lib/apt/lists/* after every apt-get install
  • No hardcoded passwords — use environment variables only

8. Secure the .env File

echo ".env" >> .gitignore

9. Final Test

make fclean
make

Then verify:

  • https://login.42.fr loads WordPress ✅
  • https://login.42.fr/wp-admin lets you log in ✅
  • Both users exist and can log in ✅
  • docker ps shows 3 running containers ✅
  • docker volume ls shows 2 volumes ✅
  • docker network ls shows the inception network ✅

9. Common Errors & Fixes

Access denied for user 'root'@'localhost' (using password: NO)

Cause: A root password was set in a previous run and is now stuck in the volume. On restart, the script tries to connect as root without a password.

Fix:

make fclean
docker volume rm $(docker volume ls -q)
make

Also make sure your MariaDB init.sh uses an if check to only run setup once (see Phase 4).

ERROR 2002: Can't connect to local MySQL server through socket

Cause: MariaDB didn't start properly inside the container.

Fix: Replace service mysql start with:

mysqld_safe &
sleep 3

unable to bind listening socket for address '/run/php/php7.4-fpm.sock'

Cause: The /run/php/ directory doesn't exist inside the container.

Fix: Add before starting PHP-FPM:

mkdir -p /run/php

Database connection error (2002) Connection refused

Cause: WordPress container starts before MariaDB is fully ready, or MariaDB is only binding to 127.0.0.1.

Fix 1 — Wait for MariaDB in WordPress init.sh:

until mysqladmin ping -h mariadb -u ${MYSQL_USER} -p${MYSQL_PASSWORD} --silent 2>/dev/null; do
sleep 1
done

Fix 2 — Make MariaDB listen on all interfaces:

RUN sed -i 's/bind-address\s*=\s*127.0.0.1/bind-address = 0.0.0.0/' \
/etc/mysql/mariadb.conf.d/50-server.cnf

WordPress Keeps Redirecting to Wrong URL

Cause: WP_HOME and WP_SITEURL are not set, so WordPress uses the URL from the database which doesn't include your port or domain.

Fix: Add after wp config create:

wp config set WP_HOME "https://${DOMAIN_NAME}" --path=/var/www/html --allow-root
wp config set WP_SITEURL "https://${DOMAIN_NAME}" --path=/var/www/html --allow-root

Container Exits Immediately (code 1 or 78)

Cause: A command in init.sh failed and the script exited.

Fix: Check the logs:

docker logs <container_id>

The last error message in the log is where the script failed.

wordpress container fails because MariaDB isn't ready

Cause: depends_on in Docker Compose only waits for the container to start, not for the service inside it to be ready.

Fix: Use a wait loop in WordPress init.sh (see above). depends_on is not enough on its own.

10. Final Checklist

Containers

  • [ ] 3 containers: nginx, wordpress, mariadb
  • [ ] Each container runs exactly one service
  • [ ] restart: always on all containers
  • [ ] Every container ends with exec (PID 1)
  • [ ] No tail -f /dev/null, no infinite loops as main process
  • [ ] No pre-built service images (only FROM debian:bullseye)

NGINX

  • [ ] Listens on port 443 only (no port 80)
  • [ ] TLSv1.2 and TLSv1.3 only
  • [ ] Self-signed certificate generated inside container
  • [ ] Only NGINX has an exposed port

WordPress

  • [ ] Two users: one admin, one editor/author/subscriber
  • [ ] Admin username does NOT contain "admin" or "administrator"
  • [ ] wp-config.php uses environment variables
  • [ ] WP_HOME and WP_SITEURL are set correctly

MariaDB

  • [ ] Database and user created automatically on first start
  • [ ] Root password set from environment variable
  • [ ] Listens on 0.0.0.0 (not just 127.0.0.1)
  • [ ] Setup only runs once (idempotent init script)

Docker Compose & Volumes

  • [ ] Single docker-compose.yml in srcs/
  • [ ] Custom bridge network named inception
  • [ ] 2 volumes: wordpress files and database data
  • [ ] Volumes bind-mounted at /home/login/data/
  • [ ] .env file used for all secrets
  • [ ] .env is in .gitignore

Domain

  • [ ] DOMAIN_NAME=login.42.fr in .env (your 42 login)
  • [ ] 127.0.0.1 login.42.fr in /etc/hosts on the VM
  • [ ] https://login.42.fr loads WordPress
  • [ ] https://login.42.fr/wp-admin login works

Makefile

  • [ ] make builds and starts everything
  • [ ] make down stops everything
  • [ ] make fclean removes containers, images, and volumes
  • [ ] make re does a full clean rebuild

Official Documentation

Tools & Downloads

Learning Resources

Final tip: If something breaks, the first thing to always do is docker logs <container_name>. The answer is almost always in the logs. Good luck — and remember: containers are just processes wearing a disguise. 🐳