From 59bbbf551645e618a8e701faf271c35c9c91296d Mon Sep 17 00:00:00 2001 From: jwarnier Date: Tue, 1 Sep 2026 03:22:46 +0200 Subject: [PATCH 1/2] Switch to PHP 8.3-FPM + nginx single-container image MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the dead PHP 5 / ubuntu 14.04 image (which cannot run the current Symfony 7 / PHP 8 LMS) with a single-container image: - PHP 8.3-FPM (www pool on 127.0.0.1:9000) - nginx (HTTP :80) serving statics + proxying .php to FPM - LMS source fetched at build time at a pinned ref (CHAMILO_LMS_REF), not vendored — keeps this repo slim and the image reproducible - memory_limit=-1 so Symfony's assets:install doesn't OOM - git dropped (all Composer deps ship zip dists) Compose (from our local setup): chamilo + MariaDB 11 + Redis 7, with DATABASE_* env names the LMS actually reads. Adds docker-compose.yml, entrypoint.sh, nginx.conf; removes the obsolete Apache vhost (000-default.conf); rewrites README/SETUP. --- .dockerignore | 11 +++- 000-default.conf | 36 ----------- Dockerfile | 145 ++++++++++++++++++++++++++------------------- README.md | 113 ++++++++++++++++------------------- SETUP.md | 92 ++++++++++++++++++++++------ docker-compose.yml | 51 ++++++++++++++++ entrypoint.sh | 15 +++++ nginx.conf | 38 ++++++++++++ 8 files changed, 323 insertions(+), 178 deletions(-) delete mode 100644 000-default.conf create mode 100644 docker-compose.yml create mode 100644 entrypoint.sh create mode 100644 nginx.conf diff --git a/.dockerignore b/.dockerignore index 36c5a45..518440e 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,2 +1,11 @@ -# Ignore items for docker build +# Build-context hygiene for the docker-chamilo image. +# This repo is docker-only: the LMS source is fetched at build time (curl), +# not copied from context, so the context only needs Dockerfile + nginx.conf +# + entrypoint.sh. Exclude everything else to keep the context lean. +.git +.github +*.log tmp/ +# Docs live in the repo for humans; the build never needs them in-context. +AGENTS.md +SETUP.md diff --git a/000-default.conf b/000-default.conf deleted file mode 100644 index e4138f9..0000000 --- a/000-default.conf +++ /dev/null @@ -1,36 +0,0 @@ - - # The ServerName directive sets the request scheme, hostname and port that - # the server uses to identify itself. This is used when creating - # redirection URLs. In the context of virtual hosts, the ServerName - # specifies what hostname must appear in the request's Host: header to - # match this virtual host. For the default virtual host (this file) this - # value is not decisive as it is used as a last resort host regardless. - # However, you must set it for any further virtual host explicitly. - #ServerName www.example.com - - ServerAdmin webmaster@localhost - DocumentRoot /var/www/html/chamilo2/public - # Available loglevels: trace8, ..., trace1, debug, info, notice, warn, - # error, crit, alert, emerg. - # It is also possible to configure the loglevel for particular - # modules, e.g. - #LogLevel info ssl:warn - - ErrorLog ${APACHE_LOG_DIR}/error.log - CustomLog ${APACHE_LOG_DIR}/access.log combined - - # For most configuration files from conf-available/, which are - # enabled or disabled at a global level, it is possible to - # include a line for only one particular virtual host. For example the - # following line enables the CGI configuration for this host only - # after it has been globally disabled with "a2disconf". - #Include conf-available/serve-cgi-bin.conf - - - AllowOverride All - Require all granted - - - - -# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/Dockerfile b/Dockerfile index f851c5e..dd13317 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,61 +1,84 @@ -FROM ubuntu:14.04 -MAINTAINER Yannick Warnier - -# Keep upstart from complaining -RUN dpkg-divert --local --rename --add /sbin/initctl -RUN ln -sf /bin/true /sbin/initctl - -# Update Ubuntu and install basic PHP stuff -RUN apt-get -y update && apt-get install -y \ - curl \ - git \ - libapache2-mod-php5 \ - php5-cli \ - php5-curl \ - php5-gd \ - php5-intl \ - php5-mysql \ - wget - -RUN apt-get install -y openssh-server -RUN mkdir -p /var/run/sshd - -# Get Chamilo -RUN mkdir -p /var/www/chamilo -ADD https://github.com/chamilo/chamilo-lms/archive/v1.10.0-alpha.tar.gz /var/www/chamilo/chamilo.tar.gz -WORKDIR /var/www/chamilo -RUN tar zxf chamilo.tar.gz;rm chamilo.tar.gz;mv chamilo* www -WORKDIR www -RUN chown -R www-data:www-data \ - app \ - main/default_course_document/images \ - main/lang \ - vendor \ - web - -# Get Composer (putting the download in /root is discutible) -WORKDIR /root -RUN curl -sS https://getcomposer.org/installer | php -RUN chmod +x composer.phar -RUN mv composer.phar /usr/local/bin/composer - -# Get Chash -RUN git clone https://github.com/chamilo/chash.git chash -WORKDIR chash -RUN composer update --no-dev -RUN php -d phar.readonly=0 createPhar.php -RUN chmod +x chash.phar && mv chash.phar /usr/local/bin/chash - -# Configure and start Apache -ADD chamilo.conf /etc/apache2/sites-available/chamilo.conf -RUN a2ensite chamilo -RUN a2enmod rewrite -RUN /etc/init.d/apache2 restart -RUN echo "127.0.0.1 docker.chamilo.net" >> /etc/hosts - -# Go to Chamilo folder and install -# Soon... (this involves having a SQL server in a linked container) - -WORKDIR /var/www/chamilo/www -EXPOSE 22 80 -CMD ["/bin/bash"] +# Chamilo LMS — single-container runtime (PHP 8.3-FPM + nginx). +# +# Slim / docker-only image: the LMS source is NOT vendored into this repo. +# It is fetched at build time from a pinned ref of chamilo/chamilo-lms, so +# the image is fully reproducible and this repo stays small. +# +# This replaces the old PHP 5 / ubuntu 14.04 image, which cannot run the +# current (Symfony 7 / PHP 8) LMS. +FROM php:8.3-fpm + +# Pinned ref of chamilo/chamilo-lms. Bump to release a new LMS version. +# Accepts a git tag (e.g. v3.0.0-beta.2) or a full commit SHA. +ARG CHAMILO_LMS_REF=c75d279bf4757617286827c5c8dae02a74f438e0 + +# System packages + PHP extensions the LMS needs. +# curl/ca-certificates : fetch the pinned source; Composer zip dists (TLS) +# nginx : serves the LMS over HTTP (front controller -> FPM) +# git is intentionally omitted — every Composer dependency in composer.lock +# ships a zip dist (no VCS-only packages), so Composer downloads archives via +# the PHP zip extension instead of cloning. +RUN apt-get update && apt-get install -y --no-install-recommends \ + curl \ + ca-certificates \ + nginx \ + libicu-dev \ + libldap-dev \ + libpng-dev \ + libonig-dev \ + libxml2-dev \ + libxslt1-dev \ + libzip-dev \ + && docker-php-ext-install -j$(nproc) \ + bcmath \ + exif \ + gd \ + intl \ + ldap \ + opcache \ + pdo \ + pdo_mysql \ + soap \ + xsl \ + zip \ + && pecl install --onlyreqdeps --force redis \ + && docker-php-ext-enable redis \ + && rm -rf /var/lib/apt/lists/* + +# Web tier: drop the stock default vhost, install ours (listens on :80, +# proxies .php to PHP-FPM at 127.0.0.1:9000, docroot /app/chamilo-lms/public). +RUN rm -f /etc/nginx/sites-enabled/default \ + && rm -rf /var/www/html +COPY nginx.conf /etc/nginx/conf.d/default.conf + +# Raise PHP memory limit for CLI and any child processes. +# Symfony's `assets:install` post-install script boots the kernel and +# exhausts the 128M default; -1 keeps the build from OOM-ing. +RUN echo "memory_limit=-1" > /usr/local/etc/php/conf.d/zz-memory.ini + +# Fetch the LMS source at the pinned ref (build-time, not vendored). +# The tarball extracts to a single top-level dir (chamilo-lms-); rename +# it to /app/chamilo-lms so the path is stable for a tag or a full SHA. +RUN curl -fsSL "https://github.com/chamilo/chamilo-lms/archive/${CHAMILO_LMS_REF}.tar.gz" -o /tmp/lms.tar.gz \ + && mkdir -p /app/lms-fetch \ + && tar -xzf /tmp/lms.tar.gz -C /app/lms-fetch \ + && mv /app/lms-fetch/chamilo-lms-* /app/chamilo-lms \ + && rm -f /tmp/lms.tar.gz \ + && rm -rf /app/lms-fetch /root/.cache + +WORKDIR /app/chamilo-lms + +# Install Composer, then PHP dependencies + the post-install asset step. +# (assets:install boots the Symfony kernel; memory_limit=-1 keeps it from +# exhausting the 128M default.) +RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer \ + && composer install --no-interaction --optimize-autoloader \ + && rm -rf /root/.composer /root/.cache/composer + +# Start PHP-FPM (daemon) + nginx (foreground, PID 1) on container start. +COPY entrypoint.sh /usr/local/bin/entrypoint.sh +RUN chmod +x /usr/local/bin/entrypoint.sh +ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] +CMD ["nginx", "-g", "daemon off;"] + +EXPOSE 80 9000 diff --git a/README.md b/README.md index 3516e73..a355a05 100644 --- a/README.md +++ b/README.md @@ -1,86 +1,75 @@ # docker-chamilo -[![](https://images.microbadger.com/badges/image/chamilo/docker-chamilo.svg)](https://microbadger.com/images/chamilo/docker-chamilo "Get your own image badge on microbadger.com") +Single-container Docker image for the [Chamilo LMS](https://www.chamilo.org). -Official Docker image for Chamilo LMS +This image bundles **PHP 8.3-FPM** and **nginx** in one container and serves the +LMS over HTTP on port **80** (nginx → PHP-FPM on `127.0.0.1:9000`). It is the +successor to the previous PHP 5 / Ubuntu 14.04 image, which cannot run the +current (Symfony 7 / PHP 8) LMS. -This image is not ready yet. Please come back soon or watch the project for updates. +## How it works -## Launching +The repo is **docker-only**: the LMS source is *not* vendored here. At build +time the `Dockerfile` downloads the LMS at a **pinned ref** of +[`chamilo/chamilo-lms`](https://github.com/chamilo/chamilo-lms) (a tag or a +full commit SHA, set by the `CHAMILO_LMS_REF` build arg) and installs its +Composer dependencies. The image is therefore fully reproducible, and this +repo stays small. -This image is currently based on Chamilo LMS 1.10 and requires a separate database container to run. -We suggest using the "mariadb" container, like so: +The container runs two processes: -``` -docker run --name mariadb -e MYSQL_ROOT_PASSWORD=pass -e MYSQL_USER=chamilo -e MYSQL_PASSWORD=chamilo -e MYSQL_DATABASE=chamilo -d mariadb -``` +| Process | Listens on | Role | +|---------|----------------|------| +| PHP-FPM | `127.0.0.1:9000` | runs the Symfony front controller | +| nginx | `0.0.0.0:80` | serves static files + proxies `.php` to FPM | -This will get you back on the command line of the Docker host. You can see the container running with ```docker ps```. +## Quick start -Then start the chamilo/docker-chamilo container: +```bash +# Build the image and start the full stack (app + MariaDB + Redis) +docker compose up -d --build -``` -docker run --link=mariadb:db --name chamilo -p 8080:80 -it chamilo/docker-chamilo +# Open the LMS +# http://localhost/ → first-run installer (create the DB, then install) ``` -At this point, the docker-chamilo image doesn't provide an installed version of Chamilo LMS, but this should be ready soon. +See [SETUP.md](SETUP.md) for first-run and production notes. -The configuration files assume the host will be "docker.chamilo.net", so you will have to define it in your host's /etc/hosts file, depending on the IP of the container. - -``` -72.17.0.10 docker.chamilo.net -``` +## Configuration -Now start your browser and load http://docker.chamilo.net. +Environment variables (read by the Symfony app — see `.env.dist` of the LMS): -## Using with a load-balancer +| Variable | Default | Notes | +|--------------------|---------|-------| +| `DATABASE_HOST` | `db` | FQDN of the database service | +| `DATABASE_PORT` | `3306` | | +| `DATABASE_NAME` | `chamilo` | | +| `DATABASE_USER` | `chamilo` | | +| `DATABASE_PASSWORD`| `chamilo` | | +| `APP_ENV` | `prod` | `dev` for verbose error pages | +| `APP_SECRET` | — | required; 32+ chars | -If you want to use a more complex system with load balancing, you might want to try out the following suite of commands: +> **Note:** the app reads `DATABASE_*`, not `DB_*`. Earlier compose examples +> used `DB_*`, which the LMS ignores. -``` -docker run --name varwww -d ywarnier/varw -``` +## Releasing a new LMS version -This will provide a shared /var/www2 partition +The version is pinned in one place — the `CHAMILO_LMS_REF` build arg in the +`Dockerfile`. To ship a new LMS version, change it to a release tag +(e.g. `v3.0.0`) or a full commit SHA, and rebuild: -``` -docker run --name mariadb -e MYSQL_ROOT_PASSWORD=pass -e MYSQL_USER=chamilo -e MYSQL_PASSWORD=chamilo -e MYSQL_DATABASE=chamilo -d mariadb -docker run --link=mariadb:db --volumes-from=varwww --name chamilo -p 8080:80 -it chamilo/docker-chamilo -# Change all configuration to point to /var/www2/chamilo/www and change the Chamilo config file (root_web) -# Also, inside app/config/configuration.php, change "session_stored_in_db" to true -# configure Chamilo on this first container then take a snapshot -docker commit -m "Live running Chamilo connected to host 'db' with existing database" {container-hash} docker-chamilo:live -docker run --link=mariadb:db --volumes-from=varwww --name chamilo2 -p 8081:80 -it docker-chamilo:live -docker run --name lb --link=chamilo:w1 --link=chamilo4:w2 -e CHAMILO_1_PORT_80_TCP_ADDR=172.17.0.10 -e CHAMILO_2_PORT_80_TCP_ADDR=172.17.0.11 -e CHAMILO_HOSTNAME=docker.chamilo.net -e CHAMILO_PATH=/ -p 8082:80 -it jasonwyatt/nginx-loadbalancer +```bash +docker build --build-arg CHAMILO_LMS_REF= -t chamilo-lms . ``` -Sadly, there's something wrong at the moment in the nginx-loadbalancer image, and you have to connect to it to change the configuration of the reverse proxy (the last container you launched). - -``` -docker ps -``` +## Requirements -(to identify the hash of the image of the load balancer (lb)) - -``` -docker exec -i -t {lb-container-hash} bash -cd /etc/nginx/sites-available/ -vi proxy.conf -``` - -(add the following *just before* proxy_pass, in the two occurrences) - -``` - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; -``` - -Now reload Nginx - -``` -service nginx reload -``` +- A MariaDB/MySQL database (provided by `docker-compose.yml` as the `db` service) +- Port 80 (HTTP) +- Optional: Redis for sessions/caching (provided as the `redis` service) -Now you should be good to go. +## Image size -Note that this will only work as long as you don't upload any file or object that needs to be stored on disk, as the two web servers will not share any disk space in the context presented above. +~1.3 GB after the source fetch (LMS source + vendor + PHP 8.3 + nginx). The +nested `.git` of the LMS source is excluded at build time, and the old +1.2 GiB `.git` from the previous approach is gone. diff --git a/SETUP.md b/SETUP.md index 105e632..55f080e 100644 --- a/SETUP.md +++ b/SETUP.md @@ -1,30 +1,86 @@ -# Creating a Chamilo 2 test containers stack +# Setup -To test Chamilo2 you can create a container based on the latest published code in GitHub as well as latest version of the base containers. **This is by no means a recommended approach for Production**. +First-run and production notes for the `docker-chamilo` image. -## Standalone Chamilo 2 container +## What's in the image -You can use the provided [Dockerfile](Dockerfile) to build your own. -Not all possible PHP extensions have been enabled but only the required ones as well as APCu as an example. +- **PHP 8.3-FPM** (the `www` pool on `127.0.0.1:9000`) +- **nginx** (HTTP on `:80`) — the web tier +- The **Chamilo LMS** source, fetched at build time at a pinned ref + (`CHAMILO_LMS_REF` in the `Dockerfile`) +- Composer dependencies + the Symfony `assets:install` step, already run -You can easily modify it to add more extensions. Layers are not squashed to make sure you can refresh Chamilo source for example by rebuilding without eventually needing refresh the previous layers. +The container starts both PHP-FPM and nginx via `entrypoint.sh`; nginx is +PID 1. -As for the database it expects you can point to yours or use a default MariaDB container when using the `docker compose up` version. +## Bring up the full stack -## Test stack (`docker compose` approach) +```bash +docker compose up -d --build +``` -Please note that you will need to create a `.env` file to define the variables of MariaDB in that case. +This starts three services (see `docker-compose.yml`): -```ini -MYSQL_ROOT_PASSWORD=securePassword -MYSQL_DATABASE=chamilo -MYSQL_USER=root -MYSQL_PASSWORD= -``` +- `chamilo` — the app (HTTP :80) +- `db` — MariaDB 11 +- `redis` — Redis 7 (sessions/cache) + +Then open **http://localhost/** — the LMS **first-run installer** walks you +through creating the database, the admin account, and completing the install. + +> The app reads `DATABASE_*` environment variables (see `.env.dist` of the +> LMS), **not** `DB_*`. The compose file sets `DATABASE_HOST=db`, etc. + +## Database + +The `db` service pre-creates a database and user: + +| Item | Value | +|------|-------| +| Root password | `chamilo` | +| Database | `chamilo` | +| User | `chamilo` | +| Password | `chamilo` | -While configuring Chamilo, use **`mariadb`** as the server hostname and whatever values you did set in the `.env` file to create the connection to the database. +For an existing database, point `DATABASE_*` at it instead. + +## Production checklist + +- Set a strong `APP_SECRET` (32+ chars). +- Terminate TLS **in front of** this container (a reverse proxy / load + balancer) — this image speaks plain HTTP on :80. +- Use a real `DATABASE_PASSWORD` and a non-root DB user. +- Back up the `db_data` volume (and `chamilo_data` for uploads). +- Pin `CHAMILO_LMS_REF` to a release tag (not a moving SHA) for + reproducible builds. +- Consider `APP_ENV=prod` (default) and disabling the debug error handler. + +## Releasing a new LMS version + +The version lives in one place — the `CHAMILO_LMS_REF` build arg in the +`Dockerfile`: + +```bash +# ship a stable release +docker build --build-arg CHAMILO_LMS_REF=v3.0.0 -t chamilo-lms . + +# or pin an exact commit +docker build --build-arg CHAMILO_LMS_REF= -t chamilo-lms . +``` -Volumes are created as named volumes to be persisted on your docker host. You can find options inside the [docker-compose.yml](docker-compose.yml) to use binded volumes or seed from an existing database export. +Rebuild and re-run `docker compose up -d --build`. The pinned ref changes +what source is fetched; everything else (PHP, extensions, nginx config) is +unchanged. -If you do not intend to rebuild every time you set up the stack, please comment out the `build` instructions in the `docker-compose.yml` section for the Chamilo container. +## Troubleshooting +- **502 / 504 from nginx** — FPM isn't up. Check `docker logs chamilo` for + the `entrypoint.sh` startup; FPM must accept on `:9000` before nginx + proxies. +- **DB connection errors** — confirm the `chamilo` service can reach `db` + (same compose network) and that `DATABASE_*` matches the `db` service. +- **`memory_limit` OOM during build** — the Dockerfile sets + `memory_limit=-1`; if you override it, `assets:install` will OOM on the + 128 M default. +- **Slow first build** — the source is downloaded at build time (~88 MB + tarball) and Composer deps are fetched; subsequent builds are cached. diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..6d21aeb --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,51 @@ +# Chamilo LMS — compose. +# +# chamilo : the app (PHP 8.3-FPM + nginx, this repo's image) — HTTP on :80 +# db : MariaDB (LMS database) +# redis : session/cache (optional, mapped to 6379) +# +# The app reads DATABASE_* (see .env.dist), NOT DB_*. Values below are +# defaults for a local dev bring-up; override for production. +services: + chamilo: + build: . + ports: + - "80:80" + environment: + - DATABASE_HOST=db + - DATABASE_PORT=3306 + - DATABASE_NAME=chamilo + - DATABASE_USER=chamilo + - DATABASE_PASSWORD=chamilo + - APP_ENV=prod + - APP_SECRET=changeme-32-chars-min-aaaaaaaa + depends_on: + - db + - redis + volumes: + - chamilo_data:/app/chamilo-lms/var + restart: unless-stopped + + db: + image: mariadb:11 + environment: + - MARIADB_ROOT_PASSWORD=chamilo + - MARIADB_DATABASE=chamilo + - MARIADB_USER=chamilo + - MARIADB_PASSWORD=chamilo + volumes: + - db_data:/var/lib/mysql + restart: unless-stopped + + redis: + image: redis:7 + ports: + - "6379:6379" + volumes: + - redis_data:/data + restart: unless-stopped + +volumes: + chamilo_data: + db_data: + redis_data: diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100644 index 0000000..6f10313 --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,15 @@ +#!/bin/sh +# Single-container entrypoint: start PHP-FPM (background) then hand PID 1 to nginx. +# nginx proxies PHP to FPM on 127.0.0.1:9000 (see nginx.conf). +set -e + +# The www pool (php:8.3-fpm default) listens on 9000. +php-fpm & + +# Wait until FPM accepts connections before nginx starts proxying to it. +until php -r 'exit((@fsockopen("127.0.0.1",9000) !== false) ? 0 : 1);' 2>/dev/null; do + sleep 0.2 +done + +# Hand PID 1 to nginx so SIGTERM/SIGQUIT reach it cleanly for graceful stop. +exec nginx -g "daemon off;" diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..2e812cf --- /dev/null +++ b/nginx.conf @@ -0,0 +1,38 @@ +# Chamilo LMS vhost — Symfony front controller -> PHP-FPM (127.0.0.1:9000). +# Installed as /etc/nginx/conf.d/default.conf (included by the http{} block). +# Serves the LMS over HTTP on :80; docroot is the Symfony public/ dir. +server { + listen 80 default_server; + server_name _; + + root /app/chamilo-lms/public; + index index.php; + + client_max_body_size 64m; + client_body_buffer_size 128k; + + # Front controller: anything that isn't a real file falls through to index.php + location / { + try_files $uri $uri/ /index.php?$args; + } + + # PHP: route all .php through the front controller + location ~ \.php$ { + fastcgi_pass 127.0.0.1:9000; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param SCRIPT_NAME /index.php; + } + + # Static assets: served directly, no PHP round-trip + location ~* \.(?:css|js|gif|svg|jpe?g|png|ico|webp|woff2?|ttf|eot|otf|map)$ { + try_files $uri =404; + access_log off; + expires 30d; + } + + # Never serve dotfiles (.env, .git, .htaccess, ...) + location ~ /\. { + deny all; + } +} From d5febfa9a8bc41fed4c45fde95e4d6bbc970c93e Mon Sep 17 00:00:00 2001 From: jwarnier Date: Tue, 1 Sep 2026 07:47:48 +0200 Subject: [PATCH 2/2] docs: add AGENTS.md agent playbook --- AGENTS.md | 90 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..5874f9e --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,90 @@ +# AGENTS.md + +Playbook for AI agents (and humans) working in this repo. Read this before +touching the Dockerfile, compose, or LMS ref. + +## What this repo is + +A **docker-only** repo: it ships a `Dockerfile` that builds a single +**PHP 8.3-FPM + nginx** container for the Chamilo LMS. The LMS source is +**not** vendored — it is **fetched at build time** at a pinned ref of +`chamilo/chamilo-lms` (the `CHAMILO_LMS_REF` build arg). Do **not** commit +the LMS source tree here; that bloats the repo and defeats the slim design. + +## Key files + +| File | Purpose | +|------|---------| +| `Dockerfile` | Builds the image. Fetches LMS at `CHAMILO_LMS_REF`, installs deps + nginx, sets up FPM. | +| `nginx.conf` | The vhost: serves `public/` statics, proxies `.php` to FPM `127.0.0.1:9000`. | +| `entrypoint.sh` | Starts `php-fpm` (bg) then `exec nginx` (PID 1). | +| `docker-compose.yml` | `chamilo` + `db` (MariaDB 11) + `redis` (Redis 7). | +| `.dockerignore` | Keeps the build context lean (docs, VCS, logs). | + +## Build + +```bash +# Default: pinned ref from the Dockerfile +docker build -t chamilo-lms . + +# Override the LMS ref (tag or full 40-char SHA) +docker build --build-arg CHAMILO_LMS_REF=v3.0.0 -t chamilo-lms . +``` + +Build is **slow the first time** (~88 MB source tarball + Composer fetch); +later builds are cached. Use `podman` if that's the host runtime. + +## Run + +```bash +docker compose up -d --build +# → http://localhost/ (first-run installer) +``` + +Verify the wiring is live (no DB yet, so expect the installer / a Symfony +error page — that **proves** nginx → FPM → PHP is connected): + +```bash +curl -s -o /dev/null -w "%{http_code}\n" http://localhost/ # 200/3xx/5xx = wired +docker exec chamilo php -r 'exit((@fsockopen("127.0.0.1",9000)!==false)?0:1);' && echo "FPM up" +docker exec chamilo nginx -t # vhost is valid +``` + +## Gotchas (do not re-learn these the hard way) + +1. **`memory_limit` OOM.** Symfony's `assets:install` post-install script boots + the kernel and exhausts PHP's 128 M default. The Dockerfile writes + `memory_limit=-1` to `/usr/local/etc/php/conf.d/zz-memory.ini`. **Do not + remove that line.** If you override memory, the build OOMs in + `PhpConfigReferenceDumpPass`. +2. **Nested `.git` bloat.** The old approach copied the LMS tree (with its + 1.2 GiB `.git`) into the image. This repo fetches a **tarball** (no `.git`), + so the image is ~1.3 GB. If you ever add a `COPY` of a source tree, you + **must** `.dockerignore` the nested `.git`. +3. **Env var names.** The app reads **`DATABASE_*`** (see `.env.dist` of the + LMS), **not** `DB_*`. The compose sets `DATABASE_HOST=db` etc. Renaming + these breaks the DB connection. +4. **FPM is on `9000`, nginx on `80`.** nginx proxies `.php` to + `127.0.0.1:9000`. If you change the FPM port, update **both** + `nginx.conf` (`fastcgi_pass`) and the `entrypoint.sh` readiness check. +5. **`entrypoint.sh` runs as root** (the image default). It must start FPM + before nginx or early requests 502. The readiness loop uses PHP's + `fsockopen` (no extra tools needed). +6. **No TLS.** The image speaks plain HTTP on :80. Terminate TLS in front of + it (reverse proxy / load balancer) for production. + +## Releasing a new LMS version + +Change **one** thing — the `CHAMILO_LMS_REF` build arg in the `Dockerfile` +(to a tag like `v3.0.0` or a full commit SHA), commit, and rebuild. Prefer a +release **tag** for reproducible public builds; a moving SHA is fine for +pinning "our exact current tree" (the current pin is a master SHA newer than +the latest tag). + +## Repo hygiene + +- Keep it **slim**: no LMS source, no `vendor/`, no build artifacts. +- Keep `.dockerignore` covering `.git`, `.github`, `*.log`, `tmp/`, and the + markdown docs (they're for humans, not the build). +- The old `000-default.conf` (Apache vhost) was removed — this image is + nginx + FPM, not Apache mod_php. Don't reintroduce Apache.