Nextcloud
En bref
Niveau : intermédiaire
Durée : 45 minutes
Prérequis : Traefik et Authentik en place (pages précédentes)
Niveau de test : reconstitué
Nextcloud, c'est quoi ?
Nextcloud est une plateforme de fichiers, d'agenda et de contacts auto-hébergée : l'équivalent libre d'un espace de stockage en ligne, avec synchronisation sur ordinateur et téléphone. C'est l'application phare du catalogue, et celle qui sert ici de modèle aux autres : un fichier Docker Compose, des labels Traefik, une connexion SSO créée par le rôle Authentik.
| Élément | Rôle |
|---|---|
| Conteneur Nextcloud | Serveur web (Apache et PHP) : interface, API, synchronisation |
| Conteneur cron | Même image, exécute les tâches de fond toutes les cinq minutes |
| PostgreSQL | Base de données (comptes, partages, métadonnées) |
| Redis | Cache et verrouillage des fichiers |
Application user_oidc |
Module Nextcloud qui délègue la connexion à Authentik |
Utilisateur → Traefik → Nextcloud → redirection vers Authentik → retour connecté
Concrètement, le rôle fait cinq choses dans l'ordre : déposer le fichier Compose, démarrer les conteneurs, attendre la fin de l'installation automatique, créer la connexion SSO dans Authentik, puis déclarer cette connexion dans Nextcloud avec la commande occ.
Cette page traite le déploiement automatisé d'une instance Nextcloud par Nébuleuse. L'installation manuelle et les commandes occ d'usage courant sont décrites dans le livre Nextcloud de la base de connaissance.
Prérequis
- Traefik publie
cloud.penhoat.example(variableclient_subdomains.nextcloud) et Authentik répond sursso.penhoat.example. nextcloudfigure dansnebuleuse_appset dansnebuleuse_catalogue.- Deux secrets existent dans le Vault du client :
vault_nextcloud_db_passwordetvault_nextcloud_admin_password(page « Inventaire et variables »).
Arborescence du rôle
roles/nextcloud/
├── defaults/main.yml
├── tasks/main.yml
└── templates/
└── docker-compose.yml.j2
Variables
# roles/nextcloud/defaults/main.yml
# Version majeure de Nextcloud : remplacer par la dernière version stable.
# Nextcloud ne permet pas de sauter une version majeure lors d'une mise à jour.
nextcloud_version: "32"
# Dossier de travail sur la VM.
nextcloud_dir: /opt/nebuleuse/nextcloud
# Nom de domaine de l'application.
nextcloud_host: "{{ client_subdomains.nextcloud }}.{{ client_domain }}"
# Secrets : définis dans le Vault, jamais en clair ici.
nextcloud_db_password: "{{ vault_nextcloud_db_password }}"
nextcloud_admin_password: "{{ vault_nextcloud_admin_password }}"
# Compte administrateur local, réservé à l'exploitation. Les utilisateurs du client
# se connectent par le SSO.
nextcloud_admin_user: nebuleuse-admin
# Réseaux d'où Nextcloud accepte l'en-tête X-Forwarded-For. Ici : les réseaux privés de Docker,
# où seul Traefik peut joindre l'application.
nextcloud_trusted_proxies: "172.16.0.0/12"
# Limites PHP : taille maximale d'un fichier envoyé et mémoire d'un processus.
nextcloud_upload_limit: 10G
nextcloud_memory_limit: 1G
# Réseau Docker partagé avec Traefik.
nextcloud_network: "{{ docker_proxy_network }}"
# Nom du fournisseur OIDC déclaré dans Nextcloud (apparaît sur le bouton de connexion).
nextcloud_oidc_provider_name: Authentik
Le fichier Docker Compose
# roles/nextcloud/templates/docker-compose.yml.j2
# Fichier géré par Ansible : toute modification manuelle sera écrasée.
# Il contient des secrets : accès réservé à root.
# Variables communes au serveur et au conteneur cron (ancre YAML réutilisée plus bas).
x-nextcloud-env: &nextcloud_env
# Base de données.
POSTGRES_HOST: db
POSTGRES_DB: nextcloud
POSTGRES_USER: nextcloud
POSTGRES_PASSWORD: "{{ nextcloud_db_password }}"
# Cache et verrouillage.
REDIS_HOST: redis
# Lues uniquement à la première installation : créent le compte administrateur local.
NEXTCLOUD_ADMIN_USER: "{{ nextcloud_admin_user }}"
NEXTCLOUD_ADMIN_PASSWORD: "{{ nextcloud_admin_password }}"
NEXTCLOUD_TRUSTED_DOMAINS: "{{ nextcloud_host }}"
# Nextcloud est derrière Traefik : il doit construire ses adresses en HTTPS
# et faire confiance à l'en-tête X-Forwarded-For envoyé par le proxy.
OVERWRITEPROTOCOL: https
OVERWRITEHOST: "{{ nextcloud_host }}"
OVERWRITECLIURL: "https://{{ nextcloud_host }}"
TRUSTED_PROXIES: "{{ nextcloud_trusted_proxies }}"
PHP_UPLOAD_LIMIT: "{{ nextcloud_upload_limit }}"
PHP_MEMORY_LIMIT: "{{ nextcloud_memory_limit }}"
services:
app:
image: nextcloud:{{ nextcloud_version }}-apache
container_name: {{ client_name }}_nextcloud
restart: unless-stopped
environment: *nextcloud_env
volumes:
# Code, configuration et données des utilisateurs.
- {{ nextcloud_dir }}/html:/var/www/html
depends_on:
- db
- redis
networks:
- {{ nextcloud_network }}
- internal
labels:
- "traefik.enable=true"
- "traefik.docker.network={{ nextcloud_network }}"
- "traefik.http.routers.nextcloud-{{ client_name }}.rule=Host(`{{ nextcloud_host }}`)"
- "traefik.http.routers.nextcloud-{{ client_name }}.entrypoints=websecure"
- "traefik.http.services.nextcloud-{{ client_name }}.loadbalancer.server.port=80"
# Redirige les adresses de découverte CalDAV et CardDAV vers le service DAV de Nextcloud.
# "$$" : un "$" littéral dans un fichier Compose.
- "traefik.http.routers.nextcloud-{{ client_name }}.middlewares=nextcloud-dav-{{ client_name }}"
- "traefik.http.middlewares.nextcloud-dav-{{ client_name }}.redirectregex.regex=https://(.*)/.well-known/(?:card|cal)dav"
- "traefik.http.middlewares.nextcloud-dav-{{ client_name }}.redirectregex.replacement=https://$${1}/remote.php/dav"
- "traefik.http.middlewares.nextcloud-dav-{{ client_name }}.redirectregex.permanent=true"
cron:
image: nextcloud:{{ nextcloud_version }}-apache
container_name: {{ client_name }}_nextcloud_cron
restart: unless-stopped
# Script fourni par l'image : lance "cron.php" toutes les cinq minutes.
entrypoint: /cron.sh
environment: *nextcloud_env
volumes:
- {{ nextcloud_dir }}/html:/var/www/html
depends_on:
- db
- redis
# Pas besoin d'être joignable : réseau interne uniquement.
networks:
- internal
db:
image: postgres:16-alpine
container_name: {{ client_name }}_nextcloud_db
restart: unless-stopped
environment:
POSTGRES_USER: nextcloud
POSTGRES_DB: nextcloud
POSTGRES_PASSWORD: "{{ nextcloud_db_password }}"
volumes:
- {{ nextcloud_dir }}/db-data:/var/lib/postgresql/data
networks:
- internal
redis:
image: redis:7-alpine
container_name: {{ client_name }}_nextcloud_redis
restart: unless-stopped
networks:
- internal
networks:
{{ nextcloud_network }}:
external: true
internal: {}
Seul le serveur Nextcloud est sur le réseau proxy. La base, Redis et le conteneur cron restent sur un réseau privé au projet. Redis n'a donc pas de mot de passe : aucun autre conteneur de la VM ne peut l'atteindre.
Déploiement
# roles/nextcloud/tasks/main.yml
- name: Créer les dossiers de Nextcloud
ansible.builtin.file:
path: "{{ item }}"
state: directory
mode: "0755"
loop:
- "{{ nextcloud_dir }}"
- "{{ nextcloud_dir }}/db-data"
# Le dossier "html" est créé par Docker : il appartient ensuite à l'utilisateur www-data
# du conteneur (UID 33). Ne pas le modifier avec chown.
- name: Déposer le fichier Docker Compose de Nextcloud
ansible.builtin.template:
src: docker-compose.yml.j2
dest: "{{ nextcloud_dir }}/docker-compose.yml"
# Le fichier contient des secrets : root uniquement.
mode: "0600"
no_log: true
- name: Démarrer Nextcloud
community.docker.docker_compose_v2:
project_src: "{{ nextcloud_dir }}"
state: present
# Au premier démarrage, l'image installe Nextcloud toute seule (plusieurs minutes).
# "occ status" ne renvoie "installed": true qu'une fois l'installation terminée.
- name: Attendre la fin de l'installation de Nextcloud
ansible.builtin.command:
argv:
- docker
- exec
- --user
- www-data
- "{{ client_name }}_nextcloud"
- php
- occ
- status
- --output=json
register: nextcloud_status
# On retire les espaces pour ne pas dépendre de la mise en forme du JSON.
until: >-
nextcloud_status.rc == 0 and
'"installed":true' in (nextcloud_status.stdout | replace(' ', ''))
retries: 60
delay: 10
changed_when: false
# Réglages complémentaires, rejouables sans effet de bord.
- name: Régler le fonctionnement de Nextcloud
ansible.builtin.command:
argv: >-
{{ ['docker', 'exec', '--user', 'www-data', client_name ~ '_nextcloud',
'php', 'occ', 'config:system:set'] + item }}
loop:
# Région par défaut des numéros de téléphone.
- ['default_phone_region', '--value=FR']
# Fenêtre de maintenance : les tâches lourdes tournent à partir de 1 h (UTC).
- ['maintenance_window_start', '--type=integer', '--value=1']
changed_when: false
- name: Utiliser le conteneur cron pour les tâches de fond
ansible.builtin.command:
argv: [docker, exec, --user, www-data, "{{ client_name }}_nextcloud", php, occ, background:cron]
changed_when: false
Connexion SSO
Cette partie du rôle appelle le fichier de tâches de la page Authentik, puis déclare le résultat dans Nextcloud. L'adresse de retour /apps/user_oidc/code est celle de l'application user_oidc.
# roles/nextcloud/tasks/main.yml (suite)
- name: Créer la connexion SSO de Nextcloud dans Authentik
ansible.builtin.include_tasks: ../../authentik/tasks/oidc_app.yml
vars:
oidc_app_name: Nextcloud
oidc_app_slug: nextcloud
oidc_redirect_uris:
- matching_mode: strict
url: "https://{{ nextcloud_host }}/apps/user_oidc/code"
- name: Lister les applications Nextcloud installées
ansible.builtin.command:
argv: [docker, exec, --user, www-data, "{{ client_name }}_nextcloud", php, occ, app:list, --output=json]
register: nextcloud_apps_list
changed_when: false
- name: Installer l'application user_oidc si elle est absente
ansible.builtin.command:
argv: [docker, exec, --user, www-data, "{{ client_name }}_nextcloud", php, occ, app:install, user_oidc]
when: "'user_oidc' not in nextcloud_apps_list.stdout"
# La commande crée le fournisseur, ou le met à jour s'il existe déjà.
# --unique-uid=0 : l'identifiant Nextcloud est le nom d'utilisateur, pas un hachage.
# --mapping-uid=... : champ du jeton utilisé comme identifiant.
- name: Déclarer Authentik comme fournisseur de connexion
ansible.builtin.command:
argv:
- docker
- exec
- --user
- www-data
- "{{ client_name }}_nextcloud"
- php
- occ
- user_oidc:provider
- "{{ nextcloud_oidc_provider_name }}"
- "--clientid={{ oidc_client_id }}"
- "--clientsecret={{ oidc_client_secret }}"
- "--discoveryuri={{ oidc_issuer }}.well-known/openid-configuration"
- "--unique-uid=0"
- "--mapping-uid=preferred_username"
- "--scope=openid email profile"
changed_when: false
no_log: true
Vérification
# Sur la VM : quatre conteneurs "Up".
docker ps --filter name=nextcloud --format '{{.Names}}\t{{.Status}}'
# Nextcloud est installé et en version attendue.
docker exec --user www-data alpha_nextcloud php occ status
# L'adresse publique répond et annonce une instance installée.
curl -sS https://cloud.penhoat.example/status.php | jq '{installed, maintenance, version}'
# Le fournisseur OIDC est déclaré.
docker exec --user www-data alpha_nextcloud php occ user_oidc:provider
# La redirection CalDAV fonctionne : l'adresse "Location" doit finir par /remote.php/dav.
curl -sSI https://cloud.penhoat.example/.well-known/caldav | grep -i '^location'
Pièges
- Variables d'installation sans effet après coup :
NEXTCLOUD_ADMIN_*, les paramètres de base et les domaines approuvés ne sont lus qu'à la première installation. Pour les changer ensuite, il faut passer parocc(par exempleconfig:system:set trusted_domains 1 --value=...). - « Accès via un domaine non approuvé » : le nom utilisé n'est pas dans
trusted_domains. Tous les noms par lesquels l'application est atteinte doivent y figurer. - Adresses en
http://ou boucle de redirection :OVERWRITEPROTOCOL,OVERWRITEHOSTouTRUSTED_PROXIESmanquent. Les journaux d'accès ne montrent alors que l'adresse de Traefik. - Utilisateurs créés avec un identifiant haché : sans
--unique-uid=0, Nextcloud nomme les comptes d'après une empreinte, illisible dans les partages et les journaux. - Erreur sur l'adresse de retour : l'adresse déclarée dans Authentik doit être exactement
https://<domaine>/apps/user_oidc/code. - Premier utilisateur SSO sans droits d'administration : le compte du client est créé à sa première connexion, comme simple utilisateur. Pour le promouvoir :
occ group:adduser admin <identifiant>. - Tâches de fond jamais exécutées : sans conteneur cron, le mode « AJAX » ne s'exécute que lorsqu'un utilisateur ouvre l'interface. Le conteneur cron et la commande
background:cronrèglent ce point. - Gros fichiers refusés : Traefik ne limite pas la taille, mais PHP oui. Les variables
PHP_UPLOAD_LIMITetPHP_MEMORY_LIMITfixent les limites ; les clients de synchronisation découpent déjà les envois. - Mise à jour de version : Nextcloud n'accepte de monter que d'une version majeure à la fois. Passer de 31 à 33 impose de passer par 32.
- Secret client visible brièvement :
--clientsecretest passé en argument dedocker execet apparaît pendant quelques secondes dans la liste des processus de la VM, accessible à root seulement. Avecno_log: true, il n'apparaît pas dans les journaux d'Ansible. - Nextcloud ne joint pas le SSO : l'erreur survient à l'échange du code de connexion. Le mécanisme d'alias réseau de Traefik, décrit dans sa page, évite ce détour par l'adresse publique.
Page suivante : Immich.