02/11/2026

Migrer un Nextcloud bare-metal vers Docker

Cet article explique comment passer d’un Nextcloud installé « à la main » (Debian, Apache + PHP 8.2, PostgreSQL local) à l’image Docker officielle avec Docker Compose, en gardant tout : comptes, fichiers, partages, calendriers, contacts, applications. Pas de réinstallation.

Il marche pour n’importe quel hôte Docker. Seul Nextcloud est concerné.

Situation de départ

Exemple réel :

Cible

Exemple de docker-compose.yml (à adapter, les mots de passe sont à mettre dans un fichier .env) :

x-nextcloud-env: &nextcloud-env
  POSTGRES_HOST: db.example.org
  POSTGRES_DB: nextcloud
  POSTGRES_USER: nextcloud
  POSTGRES_PASSWORD: ${DB_PASSWORD}
  REDIS_HOST: redis
  PHP_MEMORY_LIMIT: 2G
  PHP_UPLOAD_LIMIT: 16G
  OVERWRITEPROTOCOL: https

services:
  nextcloud:
    image: nextcloud:34.0.5-apache
    restart: unless-stopped
    depends_on: [redis]
    ports:
      - "8080:80"
    environment: *nextcloud-env
    volumes:
      - ./html:/var/www/html
      - ./data:/data/nextcloud

  cron:
    image: nextcloud:34.0.5-apache
    restart: unless-stopped
    entrypoint: /cron.sh
    depends_on: [redis]
    environment: *nextcloud-env
    volumes:
      - ./html:/var/www/html
      - ./data:/data/nextcloud

  redis:
    image: redis:7.4-alpine
    restart: unless-stopped

  collabora:
    image: collabora/code
    restart: unless-stopped
    cap_add: [MKNOD]
    ports:
      - "9980:9980"
    environment:
      aliasgroup1: ${NEXTCLOUD_URL}
      extra_params: --o:ssl.enable=false --o:ssl.termination=true

Le point important : cron reçoit exactement les mêmes variables que le service web (l’ancre YAML *nextcloud-env s’en charge). Voir plus bas pourquoi.

Le déroulé

1. Préparer l’ancienne instance

Si le disque cible est déjà chiffré, on désactive le chiffrement côté serveur :

sudo -u www-data php occ encryption:decrypt-all
sudo -u www-data php occ encryption:disable
sudo -u www-data php occ app:disable encryption

Attention, lisez la section « Chiffrement côté serveur » plus bas avant de lancer ça.

On désactive aussi les applications inutilisées :

sudo -u www-data php occ app:disable app_api
sudo -u www-data php occ app:disable recognize

2. Préparer la cible

Les dossiers html et data appartiennent à 33:33 (www-data dans l’image) :

mkdir -p html data
chown 33:33 html data

Côté PostgreSQL, on crée le rôle et la base :

CREATE ROLE nextcloud LOGIN PASSWORD '...';
CREATE DATABASE nextcloud OWNER nextcloud;

3. Pré-copie des données à chaud

C’est l’étape la plus longue (200 Go), mais elle se fait sans coupure de service :

rsync -aHAX --numeric-ids --info=progress2 /data/nextcloud/ nouvelhote:/chemin/data/

On la relancera à la bascule : seul le delta sera copié.

4. Premier démarrage de l’image

On démarre l’image une première fois pour qu’elle remplisse html avec le code de Nextcloud, sans remplir la page d’installation (on n’ouvre pas le site dans le navigateur) :

docker compose up -d nextcloud
docker compose logs -f nextcloud

Quand le code est copié, on arrête : docker compose stop.

5. Copier et adapter config.php

On copie le config.php de l’ancienne instance dans html/config/ et on l’adapte :

6. Copier les applications non livrées

Les applications installées depuis l’App Store vivent dans apps/ sur l’ancienne instance, mais l’image Docker les attend dans custom_apps/. Voir plus bas pour la liste.

7. Bascule

  1. Mode maintenance sur l’ancienne instance : occ maintenance:mode --on.

  2. Dernier rsync avec --delete.

  3. Dump et restauration de la base :

    pg_dump -Fc nextcloud > nextcloud.dump
    pg_restore --no-owner --role=nextcloud -d nextcloud nextcloud.dump
    
  4. Démarrage du conteneur, toujours en maintenance (maintenance:mode est dans le config.php copié).

  5. occ maintenance:repair.

  6. Sortie de maintenance : occ maintenance:mode --off.

  7. Bascule du reverse proxy ou du DNS vers le nouveau conteneur.

Tous les occ se lancent dans le conteneur :

docker compose exec -u www-data nextcloud php occ maintenance:repair

8. Après la migration

Quelques réglages, puis Collabora. Détails plus bas.

Les problèmes rencontrés

Chiffrement côté serveur

Après encryption:decrypt-all, il restait des fichiers chiffrés illisibles : environ 1 700 anciennes versions, des corbeilles, et un vrai fichier perdu. Solution : versions:cleanup, trashbin:cleanup, et accepter la perte.

Leçons :

Lister les applications « en plus » sans occ

Avec l’ancienne instance arrêtée, pas d’occ app:list. Mais une application « en plus » est simplement un dossier présent dans l’ancien apps/ et absent du apps/ de la nouvelle image :

OLD=/var/www/nextcloud/apps
NEW=html/apps
mkdir -p html/custom_apps
for app in $(ls "$OLD"); do
  [ -d "$NEW/$app" ] && continue
  [ "$app" = richdocumentscode ] && continue   # remplacé par Collabora
  rsync -a "$OLD/$app" html/custom_apps/
done
chown -R 33:33 html/custom_apps

On exclut ce qu’on remplace (richdocumentscode, remplacé par le conteneur Collabora).

Modifications de config.php « perdues »

Le conteneur lisait toujours dbhost => localhost, alors que j’avais modifié le fichier. Toujours vérifier ce que voit le conteneur plutôt que le fichier qu’on croit avoir modifié :

docker compose exec nextcloud grep dbhost config/config.php

Et vérifier que le bind mount pointe bien vers le bon dossier (docker compose config, docker inspect).

Le service cron oublié

Le service cron ne reçoit que les variables qu’on lui donne. Deux conséquences :

Donner au cron les mêmes variables que le service web.

Collabora

Collabora peut tourner dans le même fichier Compose que Nextcloud (plus simple, mais les redémarrages sont communs) ou dans un fichier à part.