Skip to content

mike-k-git/42_inception

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Inception

A small self-hosted infrastructure built from scratch with Docker and Docker Compose: an HTTPS WordPress site backed by PHP-FPM and MariaDB, behind an NGINX reverse proxy, with each service in its own container built from a custom Alpine image. Built as part of the 42 curriculum.

Architecture

Three core services on a single isolated bridge network. Only NGINX is exposed to the host; WordPress and the database are reachable only from inside the network.

            host :443 (TLS)
                  │
          ┌───────▼───────┐
          │     nginx     │   reverse proxy, TLS termination (TLSv1.2/1.3)
          │  (Alpine)     │   serves static, proxies *.php to PHP-FPM
          └───────┬───────┘
                  │ fastcgi
          ┌───────▼───────┐
          │   wordpress   │   PHP-FPM + WP-CLI, runs as non-root user
          │  (Alpine)     │
          └───────┬───────┘
                  │ mysql :3306
          ┌───────▼───────┐
          │    mariadb    │   database, not exposed to host
          │  (Alpine)     │
          └───────────────┘

  network: inception (bridge, static subnet)
  volumes: wordpress files + mariadb data (bind-mounted to host)
  secrets: credentials mounted at /run/secrets/* (never baked into images)

Bonus services (Redis, Adminer, FTP, a static site, MailHog) attach to the same network.

All eight containers running, with MariaDB reporting healthy:

All containers up and healthy

Bonus services

Five additional services run on the same inception network, each in its own custom Alpine container:

  • Redis – object cache for WordPress, reducing database load on repeated page renders. Reachable only inside the network (port 6379, not exposed to the host); password supplied via Docker secret.
  • Adminer – lightweight web database client for inspecting MariaDB (port 8080).
  • FTP (vsftpd) – file access to the WordPress volume over FTP with a passive-port range (21 + 21000–21010).
  • Static site – a small standalone site served independently of WordPress (port 8081), demonstrating a second web service on the same network.
  • MailHog – SMTP catch-all that intercepts mail sent by WordPress and exposes a web inbox (UI on 8025, SMTP on 1025), so outgoing mail can be tested without a real mail server.

These follow the same conventions as the core services: custom Alpine images, secrets for credentials, no host exposure unless a UI/protocol requires it.

Key engineering decisions

Secrets as files, not environment variables. All credentials are injected via Docker secrets and read from /run/secrets/<name> at runtime. Environment variables leak through docker inspect, logs, and child processes; secrets are mounted per-service as files with standard filesystem permissions, so access is scoped and auditable. No password is ever written into an image layer or the compose file.

Ordered, health-gated startup. MariaDB defines a healthcheck (a real mariadb-admin ping), and WordPress declares depends_on: condition: service_healthy. This avoids the classic race where the app starts before the database is actually accepting connections – a "started" container is not the same as a "ready" one.

Least privilege. The WordPress container creates a dedicated non-root user and runs PHP-FPM as that user. The database is never published to the host; only NGINX's 443 is exposed.

Idempotent entrypoints. Each entrypoint checks state before acting (if ! wp core is-installed, if [ ! -d /var/lib/mysql/mysql ]), so containers can stop and restart without re-bootstrapping or corrupting data. Scripts run with set -e to fail fast.

Reproducible, minimal images. Every image is built from a pinned alpine:3.22 base with only the packages each service needs, keeping images small and the attack surface low. No pre-built application images are pulled.

Persistent state via bind-mounted volumes. WordPress files and MariaDB data live in named volumes bound to ${HOME}/data/... on the host, so data survives container and image removal.

Running it

Requires Docker and Docker Compose. Configuration is provided through srcs/.env (see srcs/.env.example) and a set of secret files under secrets/:

secrets/
├─ mariadb_root_password.txt
├─ mariadb_user_name.txt
├─ mariadb_user_password.txt
├─ wordpress_admin_name.txt
├─ wordpress_admin_password.txt
├─ wordpress_admin_email.txt
├─ wordpress_user_name.txt
├─ wordpress_user_password.txt
└─ wordpress_user_email.txt
   # bonus services
├─ redis_password.txt
├─ ftp_user_name.txt
├─ ftp_user_password.txt
├─ adminer_user_name.txt
└─ adminer_user_password.txt

Then, from the project root:

make          # build images and start all containers (creates ${HOME}/data dirs)
make ps       # list running containers
make stop     # stop containers
make start    # start stopped containers
make rm       # remove stopped containers
make clean    # full teardown: remove containers, networks, images, volumes, and the host data dirs

Map the domain to localhost by adding it to /etc/hosts (matching DOMAIN in your .env):

127.0.0.1 mkugan.42.fr

The site is then served at https://<DOMAIN> and the admin area at https://<DOMAIN>/wp-admin (the TLS certificate is self-signed, so a browser warning is expected in local use).

Screenshots

WordPress served over HTTPS Self-signed TLS certificate
WordPress site over HTTPS through the NGINX reverse proxy Self-signed certificate generated in the NGINX image
MailHog inbox catching WordPress mail Adminer browsing the MariaDB tables
MailHog intercepting mail sent by WordPress Adminer connected to MariaDB, showing the WordPress tables

What I learned / next steps

This project is where Docker fundamentals clicked for me: the difference between images and containers, how namespaces and cgroups isolate processes, container networking and DNS, volume persistence, and the security trade-offs between secrets and environment variables. The natural next step is moving from single-host Docker Compose to orchestration – running the same multi-service topology on Kubernetes (deployments, services, secrets, health/readiness probes), where the startup-ordering and least-privilege ideas here map directly onto liveness/readiness probes and pod security.

Resources


AI usage: AI assisted with the initial plan of work (topics to research, ideas to test), proofreading documentation, and debugging.

About

Self-hosted multi-service infrastructure built from scratch with Docker & Docker Compose – HTTPS WordPress, NGINX, MariaDB, Redis, and more, each in a custom Alpine container. (42 project)

Topics

Resources

Stars

Watchers

Forks

Contributors