Stack docker-compose Roundcube 1.7.4 (nginx, php-fpm, MariaDB) avec skin vBlog.io

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Sa4nUG3GtbdEaPS6fA8eq1
This commit is contained in:
claude BotandClaude Opus 5.5 committed 2026-10-02 18:16:20 +00:00
commit 965e537497
21 files changed
+669

No files matched your search

+107
View File
@@ -0,0 +1,107 @@
# Roundcube Webmail en docker-compose
Stack Roundcube **1.7.4** prête à l'emploi : nginx + php-fpm + MariaDB, skin Elastic recompilé avec la charte vBlog.io (surcharges LESS dans `theme/`).
```
:8080 (127.0.0.1 par défaut, derrière un reverse proxy TLS)
│
┌─────────────▼─────────────┐ réseau frontend (sortie IMAP/SMTP)
│ nginx 1.30-alpine │ sert le statique depuis le volume www (ro)
└─────────────┬─────────────┘
│ FastCGI :9000
┌─────────────▼─────────────┐
│ roundcube (php-fpm 8.4) │ image locale = roundcube/roundcubemail:1.7.4-fpm-alpine
│ │ + skin Elastic recompilé + aspell + mailcap
└─────────────┬─────────────┘
│ réseau backend (internal: pas d'accès sortant)
┌─────────────▼─────────────┐
│ mariadb 11.8 (LTS) │
└───────────────────────────┘
```
![Login](docs/apercu-login.png)
## Démarrage
```sh
cp .env.example .env # renseigner mots de passe, RC_DES_KEY (24 car.), IMAP/SMTP
docker compose up -d --build
docker compose ps # les 3 services doivent passer "healthy"
```
Puis `http://127.0.0.1:8080/`. Le premier démarrage copie les sources dans le volume `www` et crée le schéma SQL (`bin/initdb.sh`), compter ~30 s.
## Arborescence
| Chemin | Rôle |
|---|---|
| `compose.yaml` | Les 3 services, volumes, réseaux, healthchecks |
| `.env.example` | Toutes les variables (versions, BDD, IMAP/SMTP, plugins...) |
| `roundcube/Dockerfile` | Build multi-étage : compilation LESS (node) puis image finale |
| `roundcube/zz-healthcheck.conf` | `ping.path` php-fpm pour le healthcheck |
| `nginx/templates/default.conf.template` | vhost, rendu par l'entrypoint nginx (envsubst `NGINX_*`) |
| `config/*.php` | Config Roundcube additionnelle, montée sur `/var/roundcube/config` |
| `theme/` | Surcharges du skin Elastic (`_variables.less`, `_styles.less`, `fonts/`, `images/`) |
| `scripts/sync-theme.sh` | Réimporte une customisation (défaut `../roundcube-custom/vblog`) |
## Choix techniques
- **Image officielle `fpm-alpine`** ([roundcube/roundcubemail-docker](https://github.com/roundcube/roundcubemail-docker)) : son entrypoint génère `config.docker.inc.php` depuis les `ROUNDCUBEMAIL_*`, inclut `/var/roundcube/config/*.php`, initialise/migre la base. Pas de réimplémentation.
- **Volume `www` partagé** (rw côté Roundcube, ro côté nginx) : pattern documenté upstream pour la variante FPM. À chaque démarrage, l'entrypoint resynchronise `/usr/src/roundcubemail` vers le volume (`bin/installto.sh`) : une image reconstruite (version ou thème) est donc propagée au simple `up -d`.
- **nginx** : depuis 1.7, seul `public_html/` est le docroot et les ressources sont appelées en `/static.php/<chemin>`. Le vhost les sert directement depuis le volume (préfixes et extensions alignés sur `ALLOWED_PATHS`/`SUPPORTED_TYPES` de `static.php`, cache 30 j, les URL portent `?s=<mtime>`). Seuls `index.php` et `static.php` atteignent PHP ; `installer.php` et le reste renvoient 404.
- **Thème compilé au build** : Elastic est livré en CSS minifié ; `_variables.less`/`_styles.less` ne sont pris en compte qu'à la compilation (`@import (optional)` dans `styles/variables.less` et `styles/styles.less`). Un étage `node:22-alpine` recompile `styles`, `print` et `embed` avec `lessc` (même commande que `skins/elastic/Makefile`). Aucune dépendance Node dans l'image finale.
- **Dictionnaires aspell et `mailcap` intégrés au build** plutôt qu'installés à chaque démarrage (`ROUNDCUBEMAIL_ASPELL_DICTS` fait un `apk add` au runtime). `mailcap` fournit `/etc/mime.types`, absent de l'image upstream (sinon warning « Mimetype to file extension mapping doesn't work properly »).
- **`des_key` imposée par `.env`** : sinon l'entrypoint la génère aléatoirement dans `config.inc.php`, perdue si le volume `www` est recréé.
- **Healthchecks** : MariaDB `healthcheck.sh --connect --innodb_initialized` ; Roundcube : port FPM 9000 ; nginx : `GET /fpm-ping` relayé à php-fpm (chaîne nginx → FPM validée, URL limitée à 127.0.0.1). Ordre de démarrage piloté par `depends_on: condition: service_healthy`.
- **Réseau `backend` en `internal: true`** : MariaDB n'a ni port publié ni accès sortant.
## Thème vBlog.io
Les fichiers de `theme/` et `config/20-vblog.inc.php` sont une copie de `roundcube-custom/vblog/`. Pour resynchroniser après modification :
```sh
sh scripts/sync-theme.sh [dossier_source]
docker compose up -d --build
```
Pour revenir à l'Elastic d'origine : vider `theme/`, supprimer `config/20-vblog.inc.php`, rebuild.
## Configuration
- **Variables** : voir `.env.example` (commentaires sur leur propre ligne : Compose peut intégrer un commentaire de fin de ligne à la valeur).
- **Options Roundcube** : déposer des `*.php` dans `config/` (inclus par ordre alphabétique), puis `docker compose restart roundcube`. Référence : `config/defaults.inc.php` dans le conteneur. `config/10-base.inc.php` fixe langue, `proxy_whitelist`, durée de session, rate-limit de login.
- **Plugins** : intégrés → `RC_PLUGINS` ; tiers → `RC_COMPOSER_PLUGINS` (installés par composer au démarrage, nécessite l'accès à packagist) + ajout dans `RC_PLUGINS`.
- **Taille des pièces jointes** : `UPLOAD_MAX_FILESIZE` applique la même limite à PHP et nginx. Le message encodé en base64 pèse ~1,37× la pièce jointe.
## Reverse proxy TLS
La stack écoute en HTTP sur `127.0.0.1:8080`. Le proxy frontal (Traefik, HAProxy, nginx...) doit transmettre `X-Forwarded-For` et `X-Forwarded-Proto: https`. Restreindre `proxy_whitelist` (`config/10-base.inc.php`) à l'IP du proxy en production. Si Roundcube est publié sous un sous-chemin, régler `ROUNDCUBEMAIL_REQUEST_PATH`.
## Exploitation
```sh
docker compose logs -f roundcube # logs Roundcube et php-fpm (log_driver = stdout)
# Sauvegarde : base + volume enigma si le plugin est utilisé
docker compose exec -T mariadb sh -c 'mariadb-dump -uroot -p"$MARIADB_ROOT_PASSWORD" --single-transaction "$MARIADB_DATABASE"' > roundcube-$(date +%F).sql
```
**Mise à jour Roundcube** : modifier `RC_VERSION` dans `.env` puis `docker compose up -d --build`. L'entrypoint exécute `installto.sh` puis `initdb.sh --update` (migrations SQL). Sauvegarder la base avant.
**Proxy TLS d'entreprise pendant le build** (npm, apk) : fournir sa CA en secret BuildKit, optionnel :
```sh
docker build -f roundcube/Dockerfile --secret id=ca,src=/chemin/ca.pem -t local/roundcube:1.7.4 .
docker compose up -d --no-build
```
## Validation effectuée
Stack construite et démarrée avec Docker 29.6 / Compose 5.3 : 3 services `healthy`, schéma SQL créé, login IMAP réussi contre un Dovecot 2.4.5 de test (utilisateur enregistré en base), charte vBlog appliquée (captures `docs/`), `installer.php` et `static.php/config/...` en 404, statique servi par nginx avec cache. Non testé dans l'environnement de validation (dépôt Alpine bloqué) : l'étape `apk add` des dictionnaires/mailcap, et l'envoi SMTP.
## Sources
- Image et entrypoint : https://github.com/roundcube/roundcubemail-docker et https://hub.docker.com/r/roundcube/roundcubemail
- Options de config : https://github.com/roundcube/roundcubemail/blob/master/config/defaults.inc.php
- Skin Elastic, points d'extension LESS : https://github.com/roundcube/roundcubemail/tree/master/skins/elastic
- Image nginx (templates envsubst) : https://hub.docker.com/_/nginx ; image MariaDB (`healthcheck.sh`) : https://hub.docker.com/_/mariadb