Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
59bbbf5
Switch to PHP 8.3-FPM + nginx single-container image
jwarnier Sep 1, 2026
d5febfa
docs: add AGENTS.md agent playbook
jwarnier Sep 1, 2026
9c32827
Add a CLAUDE.md just importing AGENTS.md
jwarnier Sep 4, 2026
1ec4838
Also docker-ignore the new CLAUDE.md
jwarnier Sep 4, 2026
c4108d2
Make PHP version configurable + remove useless comment
jwarnier Sep 4, 2026
1aff8b4
Merge COPY and RUN chmod lines
jwarnier Sep 4, 2026
1fa6e77
Remove some obsolete comments
jwarnier Sep 4, 2026
bae7fef
Specify the registry to use for images
jwarnier Sep 4, 2026
8c6352d
build: strip dev-only Composer packages from the prod image
jwarnier Sep 24, 2026
416b357
build: declare only the real listening port (EXPOSE 80)
jwarnier Sep 24, 2026
4d6e149
build: healthchecks + real depends_on (service_healthy)
jwarnier Sep 24, 2026
79b7101
build: scope memory_limit=-1 to the build; bound the FPM pool at runtime
jwarnier Sep 24, 2026
cd9ac7e
build: hand the app tree to the FPM runtime user (www-data)
jwarnier Sep 24, 2026
56e55d2
build: blanket *.md in .dockerignore instead of a per-file list
jwarnier Sep 24, 2026
8a8611b
docs: sync AGENTS.md with the committed Dockerfile/compose changes
jwarnier Sep 24, 2026
eb07b04
ci: add docker build job (catches fetch + memory OOM at push time)
jwarnier Sep 24, 2026
bed4d67
Bump actions/checkout to latest available major release
jwarnier Sep 24, 2026
0f0f798
ci: use docker/setup-buildx-action + docker/build-push-action
jwarnier Sep 24, 2026
9ad9650
Pin CHAMILO_LMS_REF to release tag v3.0.1
jwarnier Sep 24, 2026
325bdb7
Stop publishing Redis to the host LAN
jwarnier Sep 24, 2026
c133812
Add dependabot for actions + weekly LMS dep-drift workflow
jwarnier Sep 24, 2026
9d57a66
Merge source-fetch, composer-install, and chown into a single RUN
jwarnier Sep 24, 2026
66cc067
Move DB passwords + APP_SECRET out of the committed compose
jwarnier Sep 24, 2026
b2b05f5
Modernize dep-drift: replace hand-rolled run: scripts with trusted Gi…
jwarnier Sep 25, 2026
99f5da8
dep-drift: talk to GitHub via the gh CLI, not github-script
jwarnier Sep 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,12 @@
# 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
# (the only files the build uses are Dockerfile + nginx.conf + entrypoint.sh).
# Blanket pattern so any future .md is auto-excluded, no per-file list to keep.
*.md
21 changes: 21 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Docker Compose environment template for docker-chamilo.
#
# Copy to `.env` in this directory and edit: cp .env.example .env
# `.env` is gitignored — real values live there and are never committed.
#
# All four values below must be set: the compose file references them with
# `${VAR:?required in .env}`, so an unset value makes `docker compose` /
# `podman compose` fail at parse time instead of silently using a weak
# default.
#
# MARIADB_ROOT_PASSWORD — MariaDB root password (used by the db healthcheck).
# MARIADB_PASSWORD — password of the `chamilo` db user (MARIADB_USER).
# DATABASE_PASSWORD — password the app connects with; MUST equal
# MARIADB_PASSWORD (same `chamilo` db user).
# APP_SECRET — Symfony app secret, 32+ chars.
#
# The values shown are dev defaults — change them for any real deployment.
MARIADB_ROOT_PASSWORD=chamilo
MARIADB_PASSWORD=chamilo
DATABASE_PASSWORD=chamilo
APP_SECRET=change-me-please-make-this-32-chars-min
31 changes: 31 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Weekly Dependabot — GitHub Actions only.
#
# This repo is docker-only. Its only native-Detectable dependency manifest is
# the GitHub Actions in .github/workflows/build.yml (actions/checkout,
# docker/setup-buildx-action, docker/build-push-action). Native Dependabot
# opens a weekly PR here when any of those actions publish a new version.
#
# It deliberately does NOT cover the rest of this image's inputs, because they
# are not native Dependabot ecosystems:
# * CHAMILO_LMS_REF — a raw git ref of chamilo/chamilo-lms, fetched by the
# Dockerfile at build time. Tracked by .github/workflows/dep-drift.yml
# (weekly), which opens the drift Issue and the LMS bump PR.
# * docker-compose.yml images (mariadb:11, redis:7) — floating minors; they
# already pull the newest 11.x / 7.x on each `docker compose pull`, so
# there is no pin to manage.
# * the PHP base (FROM php:${PHP_VER}-fpm) — deliberately pinned to 8.3 (the
# point of the takeover branch), a human decision; it is also
# arg-parameterised, which native Dependabot can't match.
#
# Division of labour (so the two don't double-bump the same dependency):
# native Dependabot -> GitHub Actions (this file)
# dep-drift workflow -> the LMS pin + the weekly drift Issue
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
labels:
- "dependencies"
- "github-actions"
51 changes: 51 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# CI: the image must build.
#
# This is a docker-only repo — the only build that matters is the image, built
# with the pinned CHAMILO_LMS_REF (the Dockerfile's default ARG). This job
# runs that exact build on push and on manual dispatch, catching the two
# breakages this image is prone to instead of finding them at release time:
# * the `assets:install` memory OOM (AGENTS.md gotcha #1), and
# * a broken source fetch (pinned ref no longer present upstream).
#
# Cost: GitHub-hosted runners are free and unmetered for public repositories,
# so this is not metered against the free plan (the 2,000-min/500 MB quota
# applies only to private repos). The only real cost is build time per push;
# the LMS fetch + Composer step is the slow part and is not cached between
# runs, so each green build takes a few minutes.
name: build

on:
push:
workflow_dispatch:

# Cancel in-progress runs when a new commit lands on the same branch.
concurrency:
group: build-${{ github.ref }}
cancel-in-progress: true

jobs:
docker-build:
name: Build image
runs-on: ubuntu-latest
# A full build from scratch (LMS tarball + Composer) can exceed the
# default 1h job timeout on a cold cache; give it headroom.
timeout-minutes: 90
steps:
- name: Checkout
uses: actions/checkout@v7

# Buildx builder for docker/build-push-action. Pinned to the latest
# published minor (a bare major would float across minors on its own).
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4.4.1

# Build (and load locally) with the repo's own pinned ref — no
# build-arg override, so CI exercises exactly what a real build ships.
# load: true materialises the final image (no registry push); the build
# itself is the assertion — a fetch or OOM failure fails the step.
- name: Build image
uses: docker/build-push-action@v7.4.0
with:
context: .
load: true
tags: chamilo-lms-ci:ci
185 changes: 185 additions & 0 deletions .github/workflows/dep-drift.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
# dep-drift — weekly dependency drift for what native Dependabot can't manage.
#
# Native Dependabot (.github/dependabot.yml) only understands real manifests,
# and this docker-only repo has exactly one: the GitHub Actions in build.yml.
# The other things that can go stale — and that a dependabot run can't touch
# because they aren't a recognised ecosystem — are handled HERE, weekly:
#
# * CHAMILO_LMS_REF — a raw git ref of chamilo/chamilo-lms fetched by the
# Dockerfile at build time. We track it against the latest stable release
# tag of chamilo/chamilo-lms (AGENTS.md: "Prefer a release tag for
# reproducible public builds" — NOT master, so the image stays reproducible).
# * docker-compose.yml images — NOT managed here on purpose. mariadb:11 and
# redis:7 are floating minors; `docker compose pull` already fetches the
# newest 11.x / 7.x each time, so there is no pin to bump and nothing to
# detect. (If you ever pin them to patch versions, add them below.)
# * the PHP base (FROM php:${PHP_VER}-fpm) — deliberately pinned to 8.3 (the
# point of the takeover branch). A major PHP jump is a human decision, so
# it is left out of the automation.
#
# WHAT IT OPENS (automatically, weekly):
# * a PR — the one-line Dockerfile bump to the newest stable LMS tag, and
# * an Issue — the weekly drift report.
#
# The PR is FAIL-CLOSED: before it opens, this workflow builds the image at
# the candidate ref (the exact build from build.yml, via docker/build-
# push-action). A PR is opened only if that build succeeds. If it fails, NO
# PR is opened and the drift Issue records the failure instead — so a large
# LMS jump (the pin can be many commits behind the latest tag) never lands as
# a red PR you have to close. It is OPENED, not merged: a maintainer reviews
# the one-line diff and merges. The PR's own push re-runs build.yml as a
# second gate.
#
# The Issue is opened only when there is drift (a PR was opened, or the build
# gate failed). When the pin already equals the latest stable tag it is a
# clean no-op — no weekly "all up to date" noise.
#
# HOW IT TALKS TO GITHUB: the `gh` CLI — preinstalled on the GitHub-hosted
# ubuntu-latest runner and authenticated by GITHUB_TOKEN itself (no installer
# action, no `gh auth login`). Each GitHub operation is a readable one-liner:
# `gh api` for the release/compare lookups, `gh pr` to dedup/open the PR,
# `gh issue` to dedup/file the report. Git is the repo's own (the one-line
# Dockerfile edit + push). No hand-rolled curl/jq, no embedded JS.
#
# Cost: GitHub-hosted runners are free/unmetered for public repos, so this
# is not metered against the 2,000-min/500 MB quota (private only). The real
# cost is one cold image build per drift week; up-to-date weeks skip the build.
name: dep-drift

on:
# Mondays 06:00 UTC.
schedule:
- cron: "0 6 * * 1"
# Manual run (same as the schedule: detect latest stable tag, build-gate, open).
workflow_dispatch:

# GITHUB_TOKEN is read-only by default; grant exactly what this needs. The
# built-in token can push a fresh branch and open a PR on THIS repo (no PAT
# required) because we target the repo the workflow runs in — which is why it
# works in the fork today and would work in upstream if copied there later.
permissions:
contents: write
issues: write
pull-requests: write

env:
LMS_REPO: chamilo/chamilo-lms
# PR branch prefix; keep stable for dedup.
PR_BRANCH_PREFIX: dep-drift/chamilo-lms-

jobs:
drift:
name: Detect, build-gate, open
runs-on: ubuntu-latest
# A cold LMS build can exceed the default 1h; match build.yml's headroom.
timeout-minutes: 90
steps:
- name: Checkout
uses: actions/checkout@v7

# Same buildx builder as build.yml so this gate is byte-identical to the
# real build. Pinned to the same published minor.
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4.4.1

# Detect drift: latest stable tag vs the current pin (read out of the
# Dockerfile). `gh` is preinstalled on the runner; GITHUB_TOKEN authenticates
# it (no `gh auth login`). `releases/latest` is the latest STABLE release
# (404s if there is none — let it fail the run, don't guess).
- name: Detect drift (latest stable tag vs current pin)
id: detect
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
LATEST="$(gh api "repos/${LMS_REPO}/releases/latest" --jq .tag_name)"
CUR="$(sed -n '/^ARG[[:space:]]*CHAMILO_LMS_REF=/{s/^ARG[[:space:]]*CHAMILO_LMS_REF=//p}' Dockerfile)"
DRIFT=false
[ "$CUR" != "$LATEST" ] && DRIFT=true
{ echo "LATEST=$LATEST"; echo "CUR=$CUR"; echo "DRIFT=$DRIFT"; } >> "$GITHUB_ENV"
echo "current pin: $CUR"
echo "latest stable: $LATEST"

# Up-to-date: nothing to say. No build, no PR, no Issue.
- name: Up to date — no-op
if: env.DRIFT == 'false'
run: echo "CHAMILO_LMS_REF already equals the latest stable tag; nothing to open."

# Build at the candidate ref — the gate. Runs only when there is drift.
# Identical build to build.yml (same action, same pinning), but with the
# candidate ref as a build-arg. A failure here means NO PR is opened.
#
# NOTE: the gate conditions use `steps.gate.result` — the standard
# Actions step-status context (success/failure/cancelled/skipped). When
# DRIFT=false the gate is skipped, so result=='skipped' and neither
# downstream step runs.
- name: Build-gate at candidate ref
id: gate
if: env.DRIFT == 'true'
uses: docker/build-push-action@v7.4.0
with:
context: .
load: true
build-args: |
CHAMILO_LMS_REF=${{ env.LATEST }}
tags: dep-drift-gate:${{ env.LATEST }}

# Build failed: NO PR. File a drift Issue recording the failure (dedup by ref).
- name: File drift Issue (build FAILED — no PR opened)
if: env.DRIFT == 'true' && steps.gate.result == 'failure'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
LATEST: ${{ env.LATEST }}
run: |
set -euo pipefail
TITLE="chamilo-lms drift (build FAILED) ${LATEST}"
if gh issue list --state open --json title --jq '.[].title' | grep -Fxq "$TITLE"; then
echo "A failure Issue for ${LATEST} already exists; not duplicating."
exit 0
fi
gh issue create --title "$TITLE" --body "Build-gate FAILED building the image at CHAMILO_LMS_REF=${LATEST}; no bump PR was opened (fail-closed). The current pin is unchanged. See the dep-drift run log for the build failure."

# Build succeeded: open the one-line bump PR (dedup by branch), then file
# the weekly drift report Issue (dedup by from->to pair).
- name: Open bump PR + file drift Issue
if: env.DRIFT == 'true' && steps.gate.result == 'success'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CUR: ${{ env.CUR }}
LATEST: ${{ env.LATEST }}
PR_BRANCH_PREFIX: ${{ env.PR_BRANCH_PREFIX }}
run: |
set -euo pipefail
BRANCH="${PR_BRANCH_PREFIX}${LATEST}"
TITLE_PR="build: bump CHAMILO_LMS_REF to ${LATEST}"
# Commit identity = the workflow bot (deterministic; no local git config).
BOT_NAME="github-actions[bot]"
BOT_EMAIL="41898282+github-actions[bot]@users.noreply.github.com"

# --- The bump PR (dedup by branch: skip if one for this ref is already open).
if [ -z "$(gh pr list --state open --head "$BRANCH" --json headRefName --jq '.[].headRefName' 2>/dev/null || true)" ]; then
git checkout -b "$BRANCH"
# One-line edit: rewrite the ARG line (wherever it is), value -> newest stable.
sed -i '/^ARG[[:space:]]*CHAMILO_LMS_REF=/{s/^ARG[[:space:]]*CHAMILO_LMS_REF=.*/ARG CHAMILO_LMS_REF='"${LATEST}"'/}' Dockerfile
git add Dockerfile
git -c user.name="$BOT_NAME" -c user.email="$BOT_EMAIL" \
commit -m "$TITLE_PR" \
-m "Bumps the pinned ${LMS_REPO} ref from ${CUR} to the latest stable release ${LATEST} (one-line, fail-closed: this run built the image at ${LATEST} before opening)."
# Push with GITHUB_TOKEN (contents:write -> fresh-branch push to THIS repo is allowed).
git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@github.com/$(gh repo view --json nameWithOwner -q .nameWithOwner).git"
git push -u origin "$BRANCH"
# `--head` skips any fork/push prompt (the branch is already pushed).
gh pr create --head "$BRANCH" --title "$TITLE_PR" --body "Bumps the pinned CHAMILO_LMS_REF from ${CUR} to the latest stable release ${LATEST}. Opened automatically by the weekly dep-drift workflow, which built the image at ${LATEST} before opening it (fail-closed). The push re-runs build.yml as a second gate. Review the one-line diff; no auto-merge."
else
echo "An open PR for ${BRANCH} already exists; not opening a duplicate."
fi

# --- The weekly drift report Issue (dedup by the from->to pair).
TITLE_ISSUE="chamilo-lms drift ${CUR} -> ${LATEST}"
if gh issue list --state open --json title --jq '.[].title' | grep -Fxq "$TITLE_ISSUE"; then
echo "A drift Issue for this pair already exists; not duplicating."
else
# How many commits the latest tag is ahead of the pin (best-effort).
AHEAD="$(gh api "repos/${LMS_REPO}/compare/${CUR}...${LATEST}" --jq .ahead_by 2>/dev/null || echo '?')"
gh issue create --title "$TITLE_ISSUE" --body "Weekly dep-drift report. Current pin ${CUR}, latest stable ${LATEST} (${AHEAD} commits ahead). Build-gate PASSED at ${LATEST}; a bump PR was opened (or is already open). The change is the linked PR (one-line Dockerfile diff). Not auto-merged."
fi
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Real environment values — never committed (see .env.example for the template)
.env
36 changes: 0 additions & 36 deletions 000-default.conf

This file was deleted.

Loading