This project has been created as part of the 42 curriculum by pmolzer.
Inception is a system administration and Docker orchestration project that demonstrates the deployment of a complete web infrastructure using Docker containers. The goal is to set up a small infrastructure composed of different services following specific rules, using Docker Compose to orchestrate multiple containers.
The project implements a WordPress website with:
- NGINX web server with TLSv1.3 only
- WordPress with PHP-FPM
- MariaDB database
- All services running in separate Docker containers
- Data persistence through Docker volumes
- Secure credential management using Docker secrets
- Custom Docker network for inter-container communication
This infrastructure runs entirely in Docker containers, built from either Debian Bookworm or Alpine Linux (penultimate stable version), with no pre-built images from DockerHub (except for the base OS).
- Docker Engine (version 20.10 or higher)
- Docker Compose (version 2.0 or higher)
- Make
- Sufficient disk space (~2GB)
- Linux environment (tested on Debian/Ubuntu)
- Set up your domain name:
- Edit
/etc/hoststo add your domain:
- Edit
sudo echo "127.0.0.1 pmolzer.42.fr" >> /etc/hosts- Replace
pmolzerwith your 42 login in all configuration files
- Create secrets directory structure:
mkdir -p secrets-
Configure credentials in the
secrets/directory:db_root_password.txt- MariaDB root passworddb_password.txt- WordPress database user passwordcredentials.txt- WordPress admin credentials (format:username=admin\npassword=pass)
-
Update environment variables in
srcs/.env:DOMAIN_NAME- Your domain (e.g.,pmolzer.42.fr)- Database and WordPress configuration
# Build and start all services
make
# Or step by step:
make build # Build Docker images
make up # Start containers
# Stop services
make down
# Clean Docker system
make clean
# Full cleanup (removes volumes and data)
make fclean
# Rebuild from scratch
make re- WordPress website: https://pmolzer.42.fr
- WordPress admin panel: https://pmolzer.42.fr/wp-admin
- Database: Accessible only from within the Docker network (port 3306)
# Check all containers are running
docker ps
# Check logs
docker logs nginx
docker logs wordpress
docker logs mariadb
# Test website
curl -k https://pmolzer.42.fr- Docker Documentation
- Docker Compose Documentation
- NGINX Documentation
- WordPress Documentation
- MariaDB Documentation
- WP-CLI Documentation
- Docker Best Practices
- Understanding Docker Networking
- Docker Secrets Management
- NGINX as Reverse Proxy
- PHP-FPM Configuration
AI (Claude) was used in this project for:
Understanding & Research:
- Clarifying Docker networking concepts (bridge networks, DNS resolution)
- Understanding Docker Compose service orchestration
- Learning Docker secrets vs environment variables best practices
- Researching TLS 1.3 configuration in NGINX
Debugging & Troubleshooting:
- Resolving MariaDB container restart loops after system reboot
- Fixing WordPress-MariaDB connection issues (authentication failures)
- Debugging permission issues on mounted volumes
- Troubleshooting NGINX 403 Forbidden errors
Code Review & Optimization:
- Reviewing entrypoint scripts for idempotency
- Optimizing Dockerfile layer caching
- Improving Makefile commands
- Validating security practices (secrets management, TLS configuration)
Documentation:
- Structuring technical documentation
- Explaining complex concepts (VMs vs Docker, networking, volumes)
- Creating user and developer guides
NOT generated by AI:
- Core Dockerfiles (written manually, reviewed by AI)
- Docker Compose orchestration logic (manual design)
- All passwords and secrets (manually created)
This project uses Docker containerization to isolate services and ensure reproducibility. Each service (NGINX, WordPress, MariaDB) runs in its own container with:
- Isolated filesystem: Each container has its own file system based on its image
- Process isolation: Services run independently without interfering with each other
- Resource limits: Containers can be constrained in CPU and memory usage
- Portability: The entire stack can run on any system with Docker installed
inception/
├── Makefile # Build and orchestration commands
├── secrets/ # Docker secrets (not in Git)
│ ├── db_root_password.txt
│ ├── db_password.txt
│ └── credentials.txt
└── srcs/
├── .env # Environment variables
├── docker-compose.yml # Service orchestration
└── requirements/
├── mariadb/
│ ├── Dockerfile # MariaDB image definition
│ ├── conf/
│ │ └── 50-server.cnf # MariaDB configuration
│ └── tools/
│ └── entrypoint.sh # Initialization script
├── nginx/
│ ├── Dockerfile # NGINX image definition
│ ├── conf/
│ │ └── nginx.conf # NGINX site configuration
│ └── tools/
│ └── entrypoint.sh # SSL generation script
└── wordpress/
├── Dockerfile # WordPress image definition
├── conf/
│ ├── www.conf # PHP-FPM pool configuration
│ └── custom.ini # PHP settings
└── tools/
└── entrypoint.sh # WordPress installation script
Choice: Use debian:bookworm for all services
Rationale:
- Stable and well-tested distribution
- Rich package ecosystem (easier to install MariaDB, NGINX, PHP)
- Consistent base across all containers
- Better compatibility than Alpine for some packages
Alternative considered: Alpine Linux (smaller, but occasionally has compatibility issues)
Choice: Use bash scripts for runtime initialization
Rationale:
- Secrets only available at runtime (can't access during build)
- Need to check if database is initialized (idempotency)
- Must wait for service dependencies (WordPress waits for MariaDB)
- Dynamic configuration based on environment variables
Why not in Dockerfile: Build time vs runtime separation - Dockerfiles define "what it is," entrypoints define "how it behaves"
Choice: Use WP-CLI instead of web-based installation
Rationale:
- Fully automated installation (no manual clicks)
- Scriptable and reproducible
- Can create users programmatically
- Industry standard for WordPress automation
Choice: Restrict NGINX to TLS 1.3 only
Rationale:
- Maximum security (project requirement)
- Removes legacy protocol vulnerabilities
- Modern browsers all support TLS 1.3
- Demonstrates security best practices
Choice: Use Docker bridge network for inter-container communication
Rationale:
- Automatic DNS resolution (services find each other by name)
- Network isolation (containers can't access host directly)
- Standard pattern for multi-container applications
- Better security than host network
| Aspect | Virtual Machines | Docker Containers |
|---|---|---|
| Isolation Level | Full OS isolation with hypervisor | Process-level isolation with shared kernel |
| Boot Time | Minutes (full OS boot) | Seconds (process start) |
| Size | Gigabytes (includes full OS) | Megabytes (only app + dependencies) |
| Resource Usage | High (each VM has dedicated RAM/CPU) | Low (shares host resources efficiently) |
| Portability | Limited (VM images are large) | High (images are small, standardized) |
| Use Case | Run different OS, strong isolation | Microservices, development, CI/CD |
| Performance | Slower (virtualization overhead) | Near-native (minimal overhead) |
Why Docker for this project:
- ✅ Lightweight (can run 3 services on modest hardware)
- ✅ Fast iteration (rebuild images in seconds)
- ✅ Portable (runs identically on any Docker host)
- ✅ Efficient (shares host kernel, minimal overhead)
- ✅ Modern standard for web application deployment
When to use VMs instead:
- Need to run different operating systems (Windows + Linux)
- Require complete isolation (security-critical applications)
- Legacy applications that can't be containerized
| Aspect | Docker Secrets | Environment Variables |
|---|---|---|
| Security | Encrypted at rest, in memory only | Visible in docker inspect, logs |
| Storage | Mounted as files in /run/secrets/ |
Set as env vars in container |
| Scope | Swarm and Compose | Any Docker environment |
| Visibility | Not visible in docker inspect |
Visible in docker inspect |
| Best For | Passwords, API keys, certificates | Configuration, non-sensitive data |
| Rotation | Easy (update secret, restart service) | Requires rebuild or restart |
Our implementation:
secrets:
db_password:
file: ../secrets/db_password.txt # Stored securely
environment:
MYSQL_DATABASE: wordpress # Non-sensitive configWhy this split:
- 🔒 Secrets: Passwords (db_password, db_root_password, credentials) - never exposed
- 🔓 Env vars: Database name, domain, email - not sensitive, easier to change
Example attack scenario:
- ❌ Env var: Attacker runs
docker inspect wordpress→ sees password - ✅ Secret: Attacker runs
docker inspect wordpress→ password not visible
| Aspect | Docker Network (Bridge) | Host Network |
|---|---|---|
| Isolation | Containers isolated from host | Container uses host network directly |
| DNS Resolution | Automatic (service names work) | No DNS (must use localhost) |
| Port Conflicts | No conflicts (internal ports) | Ports conflict with host services |
| Security | Better (network isolation) | Weaker (direct host access) |
| Performance | Slight overhead (NAT) | Native (no NAT) |
| Use Case | Multi-container apps | Network monitoring, high performance |
Our choice: Bridge network
networks:
inception:
driver: bridgeWhy bridge network:
- ✅ WordPress connects to MariaDB using
mariadb:3306(DNS works) - ✅ NGINX connects to WordPress using
wordpress:9000(DNS works) - ✅ MariaDB port 3306 not exposed to host (security)
- ✅ Only NGINX port 443 exposed (minimal attack surface)
With host network (what we avoided):
- ❌ No automatic DNS (must use
localhostor IP addresses) - ❌ Port conflicts (if host has MariaDB, conflict on 3306)
- ❌ Less secure (containers can access all host network)
Network flow in our project:
Internet → Host:443 → NGINX:443 → WordPress:9000 → MariaDB:3306
(exposed) (bridge net) (bridge net) (isolated)
| Aspect | Docker Volumes | Bind Mounts |
|---|---|---|
| Management | Docker managed | User managed |
| Location | Docker area (/var/lib/docker/volumes/) |
Any host path |
| Portability | Portable (Docker manages path) | Not portable (hardcoded paths) |
| Permissions | Docker handles permissions | Manual permission management |
| Backup | Use docker volume commands |
Standard file backup |
| Performance | Optimized by Docker | Native filesystem performance |
| Use Case | Database data, production | Development, config files |
Our implementation: Bind mounts with volume abstraction
volumes:
mariadb:
driver: local
driver_opts:
type: none
o: bind
device: /home/pmolzer/data/mariadb # Bind mount
wordpress:
driver: local
driver_opts:
type: none
o: bind
device: /home/pmolzer/data/wordpress # Bind mountWhy this hybrid approach:
- ✅ Explicit control: We know exactly where data is stored
- ✅ Easy backup: Can directly backup
/home/pmolzer/data/ - ✅ Debugging: Can inspect files directly on host
- ✅ Project requirement: Must use bind mounts to specific paths
- ✅ Persistence: Data survives container deletion
Comparison for our data:
If we used pure Docker volumes:
volumes:
mariadb: # Docker creates volume automatically
wordpress:- ✅ Portable
- ✅ Better permissions handling
- ❌ Data location unclear (
/var/lib/docker/volumes/srcs_mariadb/_data) - ❌ Harder to backup
- ❌ Doesn't meet project requirements
Our bind mount approach:
volumes:
mariadb:
driver_opts:
device: /home/pmolzer/data/mariadb- ✅ Clear data location
- ✅ Easy to backup (
cp -r /home/pmolzer/data/ backup/) - ✅ Meets project requirements
⚠️ Must manually fix permissions after reboot⚠️ Path hardcoded (less portable)
Data persistence verification:
# Create post in WordPress
docker exec -it wordpress wp post create --post_title="Test" --allow-root
# Restart entire system
make down
make up
# Post still exists (data persisted in /home/pmolzer/data/wordpress)
docker exec -it wordpress wp post list --allow-root| Decision | Choice | Rationale |
|---|---|---|
| Containerization | Docker | Lightweight, portable, modern standard |
| Base Image | Debian Bookworm | Stable, compatible, rich packages |
| Secrets | Docker Secrets | Secure password management |
| Network | Bridge Network | Isolation + DNS resolution |
| Volumes | Bind Mounts | Explicit control, easy backup |
| TLS | TLS 1.3 only | Maximum security |
| Orchestration | Docker Compose | Declarative, reproducible |
This architecture balances security (secrets, TLS 1.3, network isolation), maintainability (clear structure, automation), and performance (efficient containerization, static file serving).
This is an educational project created for the 42 school curriculum.