Skip to content

Installer Nextcloud avec docker-compose sur Debian 10 avec Nginx en reverse-proxy

1. Installer nginx

Nginx est un serveur web, comme Apache.
Nous allons l'utiliser comme reverse-proxy, c'est-à-dire qu'il renverra les requêtes d'un autre serveur, que nous installerons aux côtés de wordpress.

Ainsi nous pourrons utiliser le serveur de notre choix pour notre web-application Wordpress (Apache ou Nginx).

On installe Nginx à partir des dépôts Debian, de façon à avoir une version stable, avec l'apport des correctifs de sécurité, et qui ne sera jamais cassé par une modification du système.

sudo apt update
sudo apt upgrade
sudo apt install nginx

Dans votre navigateur, en tapant dabs la barre d'adresse http://IP_DU_SERVEUR, vous devriez accéder à la page d'accueil de nginx Welcome To Nginx!

2. Installation de docker

2.1 Installation des dépendances

sudo apt update
sudo apt -y install apt-transport-https ca-certificates curl gnupg2 software-properties-common

2.2 Installation de la clé du dépôt Docker

curl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key add -

2.3 Ajout du dépôt docker dans le sources.list

sudo add-apt-repository \
        "deb [arch=amd64] https://download.docker.com/linux/debian \
        $(lsb_release -cs) \
        stable"

2.4 Mise à jour de la liste des paquets et installation de docker

sudo apt update && sudo apt install docker-ce docker-ce-cli containerd.io

2.5 Ajout de l'utilisateur dans le groupe docker pour utiliser docker sans sudo

sudo usermod -aG docker $USER
newgrp docker

3. Installation de Docker-Compose

3.1 Téléchargement de Docker-Compose

curl -s https://api.github.com/repos/docker/compose/releases/latest \
  | grep browser_download_url \
  | grep docker-compose-linux-x86_64 \
  | cut -d '"' -f 4 \
  | wget -qi -

3.2 Mise en place de l'exécutable

chmod +x docker-compose-linux-x86_64 && sudo mv docker-compose-linux-x86_64 /usr/local/bin/docker-compose
sudo systemctl restart docker

3.3 Auto-complétion pour Bash

sudo mkdir -p /etc/bash_completion.d
sudo curl -L https://raw.githubusercontent.com/docker/compose/master/contrib/completion/bash/docker-compose -o /etc/bash_completion.d/docker-compose
source /etc/bash_completion.d/docker-compose

Docker et Docker-compose sont maintenant installés

4. Mise en place de Nextcloud, collabora, etherpad, coturn, redis avec docker-compose

4.1 Création d'un dossier pour le projet

mkdir nextcloud && cd nextcloud

4.2 Fichiers de configuration pour nextcloud

Nous avons besoin de créer les fichiers suivants pour qu'ils soient utilisés par l'image de nextcloud:

4.2.1 config.php

config.php
<?php
$CONFIG = array (
  'htaccess.RewriteBase' => '/',
  'memcache.local' => '\\OC\\Memcache\\APCu',
  'apps_paths' => 
  array (
    0 => 
    array (
      'path' => '/var/www/html/apps',
      'url' => '/apps',
      'writable' => false,
    ),
    1 => 
    array (
      'path' => '/var/www/html/custom_apps',
      'url' => '/custom_apps',
      'writable' => true,
    ),
  ),
  'memcache.distributed' => '\\OC\\Memcache\\Redis',
  'memcache.locking' => '\\OC\\Memcache\\Redis',
  'redis' => 
  array (
    'host' => 'redisnext',
    'port' => 6379,
    'password' => false,
  ),
  'trusted_domains' => 
  array (
    0 => 'nextcloud.mondomaine.fr',
    1 => 'nextcloud',
  ),
  'trusted_proxies' => 
  array (
    0 => '172.17.0.1',
    1 => '172.22.0.1',
    2 => '172.23.0.1',
    3 => '172.21.0.1',
  ),
  'skeletondirectory' => '',
  'datadirectory' => '/data',
  'dbtype' => 'mysql',
  'overwrite.cli.url' => 'https://nextcloud.mondomaine.fr',
  'dbname' => 'dbnext',
  'dbhost' => 'databasenext',
  'dbport' => '',
  'dbtableprefix' => 'oc_',
  'mysql.utf8mb4' => true,
  'dbuser' => 'nextclouduser',
  'dbpassword' => 'azdqsdokpokpokds098098',
  'installed' => true,
  'loglevel' => 2,
  'maintenance' => false,
  'app_install_overwrite' => 
  array (
    0 => 'dashboard',
    1 => 'files_clipboard',
    2 => 'afterlogic',
    3 => 'ownpad',
  ),
  'mail_from_address' => 'contact',
  'mail_smtpmode' => 'smtp',
  'mail_sendmailmode' => 'smtp',
  'mail_domain' => 'mondomaine.fr',
  'mail_smtphost' => 'mail.mymailprovider.net',
  'mail_smtpsecure' => 'tls',
  'mail_smtpport' => '587',
  'mail_smtpauthtype' => 'LOGIN',
  'mail_smtpauth' => 1,
  'mail_smtpname' => 'contact@mondomaine.fr',
  'mail_smtppassword' => 'MYSMTPPASSWORD',
  'has_rebuilt_cache' => true,
  'overwriteprotocol' => 'https',
  'theme' => '',
  'default_language' => 'fr',
  'default_locale' => 'fr_FR',
  'defaultapp' => 'files',
);

4.2.2 Internal.php

Internal.php
<?php

declare(strict_types=1);

/**
 * @copyright Copyright (c) 2016, ownCloud, Inc.
 *
 * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
 * @author cetra3 <peter@parashift.com.au>
 * @author Christoph Wurst <christoph@winzerhof-wurst.at>
 * @author Lukas Reschke <lukas@statuscode.ch>
 * @author MartB <mart.b@outlook.de>
 * @author Morris Jobke <hey@morrisjobke.de>
 * @author Robin Appelman <robin@icewind.nl>
 * @author Roeland Jago Douma <roeland@famdouma.nl>
 * @author Thomas Müller <thomas.mueller@tmit.eu>
 * @author Victor Dubiniuk <dubiniuk@owncloud.com>
 *
 * @license AGPL-3.0
 *
 * This code is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License, version 3,
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License, version 3,
 * along with this program. If not, see <http://www.gnu.org/licenses/>
 *
 */

namespace OC\Session;

use OC\Authentication\Exceptions\InvalidTokenException;
use OC\Authentication\Token\IProvider;
use OCP\Session\Exceptions\SessionNotAvailableException;

/**
 * Class Internal
 *
 * wrap php's internal session handling into the Session interface
 *
 * @package OC\Session
 */
class Internal extends Session {
        /**
         * @param string $name
         * @throws \Exception
         */
        public function __construct(string $name) {
                set_error_handler([$this, 'trapError']);
                $this->invoke('session_name', [$name]);
                try {
                        $this->startSession();
                } catch (\Exception $e) {
                        setcookie($this->invoke('session_name'), '', -1, \OC::$WEBROOT ?: '/');
                }
                restore_error_handler();
                if (!isset($_SESSION)) {
                        throw new \Exception('Failed to start session');
                }
        }

        /**
         * @param string $key
         * @param integer $value
         */
        public function set(string $key, $value) {
                $this->validateSession();
                $_SESSION[$key] = $value;
        }

        /**
         * @param string $key
         * @return mixed
         */
        public function get(string $key) {
                if (!$this->exists($key)) {
                        return null;
                }
                return $_SESSION[$key];
        }

        /**
         * @param string $key
         * @return bool
         */
        public function exists(string $key): bool {
                return isset($_SESSION[$key]);
        }
        /**
         * @param string $key
         */
        public function remove(string $key) {
                if (isset($_SESSION[$key])) {
                        unset($_SESSION[$key]);
                }
        }

        public function clear() {
                $this->invoke('session_unset');
                $this->regenerateId();
                $_SESSION = [];
        }

        public function close() {
                $this->invoke('session_write_close');
                parent::close();
        }

        /**
         * Wrapper around session_regenerate_id
         *
         * @param bool $deleteOldSession Whether to delete the old associated session file or not.
         * @param bool $updateToken Wheater to update the associated auth token
         * @return void
         */
        public function regenerateId(bool $deleteOldSession = true, bool $updateToken = false) {
                $oldId = null;

                if ($updateToken) {
                        // Get the old id to update the token
                        try {
                                $oldId = $this->getId();
                        } catch (SessionNotAvailableException $e) {
                                // We can't update a token if there is no previous id
                                $updateToken = false;
                        }
                }

                try {
                        @session_regenerate_id($deleteOldSession);
                } catch (\Error $e) {
                        $this->trapError($e->getCode(), $e->getMessage());
                }

                if ($updateToken) {
                        // Get the new id to update the token
                        $newId = $this->getId();

                        /** @var IProvider $tokenProvider */
                        $tokenProvider = \OC::$server->query(IProvider::class);

                        try {
                                $tokenProvider->renewSessionToken($oldId, $newId);
                        } catch (InvalidTokenException $e) {
                                // Just ignore
                        }
                }
        }

        /**
         * Wrapper around session_id
         *
         * @return string
         * @throws SessionNotAvailableException
         * @since 9.1.0
         */
        public function getId(): string {
                $id = $this->invoke('session_id', [], true);
                if ($id === '') {
                        throw new SessionNotAvailableException();
                }
                return $id;
        }

        /**
         * @throws \Exception
         */
        public function reopen() {
                throw new \Exception('The session cannot be reopened - reopen() is ony to be used in unit testing.');
        }

        /**
         * @param int $errorNumber
         * @param string $errorString
         * @throws \ErrorException
         */
        public function trapError(int $errorNumber, string $errorString) {
                throw new \ErrorException($errorString);
        }

        /**
         * @throws \Exception
         */
        private function validateSession() {
                if ($this->sessionClosed) {
                        throw new SessionNotAvailableException('Session has been closed - no further changes to the session are allowed');
                }
        }

        /**
         * @param string $functionName the full session_* function name
         * @param array $parameters
         * @param bool $silence whether to suppress warnings
         * @throws \ErrorException via trapError
         * @return mixed
         */
        private function invoke(string $functionName, array $parameters = [], bool $silence = false) {
                try {
                        if ($silence) {
                                return @call_user_func_array($functionName, $parameters);
                        } else {
                                return call_user_func_array($functionName, $parameters);
                        }
                } catch (\Error $e) {
                        $this->trapError($e->getCode(), $e->getMessage());
                }
        }

        private function startSession() {
                if (PHP_VERSION_ID < 70300) {
                        $this->invoke('session_start');
                } else {
                        $this->invoke('session_start', [['cookie_samesite' => 'Lax']]);
                }
        }
}

4.2.3 mimetypemapping.json

mimetypemapping.json
{
        "drawio": ["application/x-drawio"],
        "pad": ["application/x-ownpad"],
        "calc": ["application/x-ownpad"],
        "3gp": ["video/3gpp"],
        "7z": ["application/x-7z-compressed"],
        "accdb": ["application/msaccess"],
        "ai": ["application/illustrator"],
        "apk": ["application/vnd.android.package-archive"],
        "arw": ["image/x-dcraw"],
        "avi": ["video/x-msvideo"],
        "bash": ["text/x-shellscript"],
        "bat": ["application/x-msdos-program"],
        "blend": ["application/x-blender"],
        "bin": ["application/x-bin"],
        "bmp": ["image/bmp"],
        "bpg": ["image/bpg"],
        "bz2": ["application/x-bzip2"],
        "cb7": ["application/comicbook+7z"],
        "cba": ["application/comicbook+ace"],
        "cbr": ["application/comicbook+rar"],
        "cbt": ["application/comicbook+tar"],
        "cbtc": ["application/comicbook+truecrypt"],
        "cbz": ["application/comicbook+zip"],
        "cc": ["text/x-c"],
        "cdr": ["application/coreldraw"],
        "class": ["application/java"],
        "cmd": ["application/cmd"],
        "cnf": ["text/plain"],
        "conf": ["text/plain"],
        "cpp": ["text/x-c++src"],
        "cr2": ["image/x-dcraw"],
        "css": ["text/css"],
        "csv": ["text/csv"],
        "cvbdl": ["application/x-cbr"],
        "c": ["text/x-c"],
        "c++": ["text/x-c++src"],
        "dcr": ["image/x-dcraw"],
        "deb": ["application/x-deb"],
        "dng": ["image/x-dcraw"],
        "doc": ["application/msword"],
        "docm": ["application/vnd.ms-word.document.macroEnabled.12"],
        "docx": ["application/vnd.openxmlformats-officedocument.wordprocessingml.document"],
        "dot": ["application/msword"],
        "dotx": ["application/vnd.openxmlformats-officedocument.wordprocessingml.template"],
        "dv": ["video/dv"],
        "eot": ["application/vnd.ms-fontobject"],
        "epub": ["application/epub+zip"],
        "eps": ["application/postscript"],
        "erf": ["image/x-dcraw"],
        "exe": ["application/x-ms-dos-executable"],
        "fb2": ["application/x-fictionbook+xml", "text/plain"],
        "flac": ["audio/flac"],
        "flv": ["video/x-flv"],
        "gif": ["image/gif"],
        "gpx": ["application/gpx+xml"],
        "gz": ["application/x-gzip"],
        "gzip": ["application/x-gzip"],
        "h": ["text/x-h"],
        "heic": ["image/heic"],
        "heif": ["image/heif"],
        "hh": ["text/x-h"],
        "hpp": ["text/x-h"],
        "htaccess": ["text/plain"],
        "html": ["text/html", "text/plain"],
        "htm": ["text/html", "text/plain"],
        "ical": ["text/calendar"],
        "ics": ["text/calendar"],
        "iiq": ["image/x-dcraw"],
        "impress": ["text/impress"],
        "java": ["text/x-java-source"],
        "jp2": ["image/jp2"],
        "jpeg": ["image/jpeg"],
        "jpg": ["image/jpeg"],
        "jps": ["image/jpeg"],
        "js": ["application/javascript", "text/plain"],
        "json": ["application/json", "text/plain"],
        "k25": ["image/x-dcraw"],
        "kdbx": ["application/x-kdbx"],
        "kdc": ["image/x-dcraw"],
        "key": ["application/x-iwork-keynote-sffkey"],
        "keynote": ["application/x-iwork-keynote-sffkey"],
        "km": ["application/km"],
        "kml": ["application/vnd.google-earth.kml+xml"],
        "kmz": ["application/vnd.google-earth.kmz"],
        "kra": ["application/x-krita"],
        "ldif": ["text/x-ldif"],
        "lwp": ["application/vnd.lotus-wordpro"],
        "m2t": ["video/mp2t"],
        "m3u": ["audio/mpegurl"],
        "m3u8": ["audio/mpegurl"],
        "m4a": ["audio/mp4"],
        "m4b": ["audio/m4b"],
        "m4v": ["video/mp4"],
        "markdown": ["text/markdown"],
        "mdown": ["text/markdown"],
        "md": ["text/markdown"],
        "mdb": ["application/msaccess"],
        "mdwn": ["text/markdown"],
        "mkd": ["text/markdown"],
        "mef": ["image/x-dcraw"],
        "mkv": ["video/x-matroska"],
        "mm": ["application/x-freemind"],
        "mobi": ["application/x-mobipocket-ebook"],
        "mov": ["video/quicktime"],
        "mp3": ["audio/mpeg"],
        "mp4": ["video/mp4"],
        "mpeg": ["video/mpeg"],
        "mpg": ["video/mpeg"],
        "mpo": ["image/jpeg"],
        "msi": ["application/x-msi"],
        "mts": ["video/MP2T"],
        "mt2s": ["video/MP2T"],
        "nef": ["image/x-dcraw"],
        "numbers": ["application/x-iwork-numbers-sffnumbers"],
        "odf": ["application/vnd.oasis.opendocument.formula"],
        "odg": ["application/vnd.oasis.opendocument.graphics"],
        "odp": ["application/vnd.oasis.opendocument.presentation"],
        "ods": ["application/vnd.oasis.opendocument.spreadsheet"],
        "odt": ["application/vnd.oasis.opendocument.text"],
        "oga": ["audio/ogg"],
        "ogg": ["audio/ogg"],
        "ogv": ["video/ogg"],
        "one": ["application/msonenote"],
        "opus": ["audio/ogg"],
        "orf": ["image/x-dcraw"],
        "otf": ["application/font-sfnt"],
        "pages": ["application/x-iwork-pages-sffpages"],
        "pdf": ["application/pdf"],
        "pfb": ["application/x-font"],
        "pef": ["image/x-dcraw"],
        "php": ["application/x-php"],
        "pl": ["application/x-perl"],
        "pls": ["audio/x-scpls"],
        "png": ["image/png"],
        "pot": ["application/vnd.ms-powerpoint"],
        "potm": ["application/vnd.ms-powerpoint.template.macroEnabled.12"],
        "potx": ["application/vnd.openxmlformats-officedocument.presentationml.template"],
        "ppa": ["application/vnd.ms-powerpoint"],
        "ppam": ["application/vnd.ms-powerpoint.addin.macroEnabled.12"],
        "pps": ["application/vnd.ms-powerpoint"],
        "ppsm": ["application/vnd.ms-powerpoint.slideshow.macroEnabled.12"],
        "ppsx": ["application/vnd.openxmlformats-officedocument.presentationml.slideshow"],
        "ppt": ["application/vnd.ms-powerpoint"],
        "pptm": ["application/vnd.ms-powerpoint.presentation.macroEnabled.12"],
        "pptx": ["application/vnd.openxmlformats-officedocument.presentationml.presentation"],
        "ps": ["application/postscript"],
        "psd": ["application/x-photoshop"],
        "py": ["text/x-python"],
        "raf": ["image/x-dcraw"],
        "rar": ["application/x-rar-compressed"],        "reveal": ["text/reveal"],
        "rss": ["application/rss+xml"],
        "rtf": ["text/rtf"],
        "rw2": ["image/x-dcraw"],
        "schema": ["text/plain"],
        "sgf": ["application/sgf"],
        "sh-lib": ["text/x-shellscript"],
        "sh": ["text/x-shellscript"],
        "srf": ["image/x-dcraw"],
        "sr2": ["image/x-dcraw"],
        "svg": ["image/svg+xml", "text/plain"],
        "swf": ["application/x-shockwave-flash", "application/octet-stream"],
        "tar": ["application/x-tar"],
        "tar.bz2": ["application/x-bzip2"],
        "tar.gz": ["application/x-compressed"],
        "tbz2": ["application/x-bzip2"],
        "tcx": ["application/vnd.garmin.tcx+xml"],
        "tex": ["application/x-tex"],
        "tgz": ["application/x-compressed"],
        "tiff": ["image/tiff"],
        "tif": ["image/tiff"],
        "ttf": ["application/font-sfnt"],
        "txt": ["text/plain"],
        "vcard": ["text/vcard"],
        "vcf": ["text/vcard"],
        "vob": ["video/dvd"],
        "vsd": ["application/vnd.visio"],
        "vsdm": ["application/vnd.ms-visio.drawing.macroEnabled.12"],
        "vsdx": ["application/vnd.ms-visio.drawing"],
        "vssm": ["application/vnd.ms-visio.stencil.macroEnabled.12"],
        "vssx": ["application/vnd.ms-visio.stencil"],
        "vstm": ["application/vnd.ms-visio.template.macroEnabled.12"],
        "vstx": ["application/vnd.ms-visio.template"],
        "wav": ["audio/wav"],
        "webm": ["video/webm"],
        "webp": ["image/webp"],
        "woff": ["application/font-woff"],
        "wpd": ["application/vnd.wordperfect"],
        "wmv": ["video/x-ms-wmv"],
        "xcf": ["application/x-gimp"],
        "xla": ["application/vnd.ms-excel"],
        "xlam": ["application/vnd.ms-excel.addin.macroEnabled.12"],
        "xls": ["application/vnd.ms-excel"],
        "xlsb": ["application/vnd.ms-excel.sheet.binary.macroEnabled.12"],
        "xlsm": ["application/vnd.ms-excel.sheet.macroEnabled.12"],
        "xlsx": ["application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"],
        "xlt": ["application/vnd.ms-excel"],
        "xltm": ["application/vnd.ms-excel.template.macroEnabled.12"],
        "xltx": ["application/vnd.openxmlformats-officedocument.spreadsheetml.template"],
        "xmind": ["application/vnd.xmind.workbook"],
        "xml": ["application/xml", "text/plain"],
        "xrf": ["image/x-dcraw"],
        "yaml": ["application/yaml", "text/plain"],
        "yml": ["application/yaml", "text/plain"],
        "zip": ["application/zip"],
        "url": ["application/internet-shortcut"],
        "webloc": ["application/internet-shortcut"]
}

4.2.4 Création des dossiers pour les volumes permanents

sudo mkdir /data/nextcloud/{data,config,apps,mysql} -p
sudo chown -R www-data:root /data/nextcloud
sudo chown -R 999:root /data/nextcloud/mysql

4.3 Fichiers de configuration pour Redis

4.3.1 redis.conf

redis.conf
requirepass aeazddokpokqsd7676

4.3.2 redis.config.php

redis.config.php
<?php
if (getenv('REDIS_HOST')) {
  $CONFIG = array (
    'memcache.distributed' => '\OC\Memcache\Redis',
    'memcache.locking' => '\OC\Memcache\Redis',
    'redis' => array(
      'host' => getenv('REDIS_HOST'),
      'password' => getenv('REDIS_HOST_PASSWORD'),
    ),
  );

  if (getenv('REDIS_HOST_PORT') !== false) {
    $CONFIG['redis']['port'] = (int) getenv('REDIS_HOST_PORT');
  } elseif (getenv('REDIS_HOST')[0] != '/') {
    $CONFIG['redis']['port'] = 6379;
  }
}

4.4 Fichiers de configuration pour etherpad

4.4.1 Dockerfile

Nous allons compiler une image d'etherpad en suivant les instructions de Dockerfile:

Dockerfile
# Etherpad Lite Dockerfile
#
# https://github.com/ether/etherpad-lite
#
# Author: muxator

FROM node:10-buster-slim
LABEL maintainer="Etherpad team, https://github.com/ether/etherpad-lite"

# plugins to install while building the container. By default no plugins are
# installed.
# If given a value, it has to be a space-separated, quoted list of plugin names.
#
# EXAMPLE:
#   ETHERPAD_PLUGINS="ep_codepad ep_author_neat"
ARG ETHERPAD_PLUGINS=

# By default, Etherpad container is built and run in "production" mode. This is
# leaner (development dependencies are not installed) and runs faster (among
# other things, assets are minified & compressed).
ENV NODE_ENV=production

# Follow the principle of least privilege: run as unprivileged user.
#
# Running as non-root enables running this image in platforms like OpenShift
# that do not allow images running as root.
RUN useradd --uid 5001 --create-home etherpad

RUN mkdir /opt/etherpad-lite && chown etherpad:0 /opt/etherpad-lite

USER etherpad

WORKDIR /opt/etherpad-lite

COPY --chown=etherpad:0 ./ ./

# install node dependencies for Etherpad
RUN bin/installDeps.sh && \
        rm -rf ~/.npm/_cacache

# Install the plugins, if ETHERPAD_PLUGINS is not empty.
#
# Bash trick: in the for loop ${ETHERPAD_PLUGINS} is NOT quoted, in order to be
# able to split at spaces.
RUN for PLUGIN_NAME in ${ETHERPAD_PLUGINS}; do npm install "${PLUGIN_NAME}"; done

# Copy the configuration file.
COPY --chown=etherpad:0 ./settings.json.docker /opt/etherpad-lite/settings.json

# Fix permissions for root group
RUN chmod -R g=u .

EXPOSE 9001
CMD ["node", "node_modules/ep_etherpad-lite/node/server.js"]

4.5 Copie du projet etherpad sur github

git clone https://github.com/ether/etherpad-lite.git

4.6 Création du fichier de configuration docker-compose.yml:

Ce fichier de configuration va déployer une image docker de nextcloud avec collabora et etherpad, avec la base de données mariadb, redis et coturn.

docker-compose.yml
version: '3.7'

services:

  nextcloud:
    image: nextcloud:19
    depends_on:
      - databasenext
      - redisnext
    extra_hosts:
      - nextcloud.mondomaine.fr:$IP_DU_SERVEUR
      - collabora.mondomaine.fr:$IP_DU_SERVEUR
      - pad.mondomaine.fr:$IP_DU_SERVEUR
    environment:
      - NEXTCLOUD_DATA_DIR=/data
      - VIRTUAL_HOST=nexctloud.mondomaine.fr
      - VIRTUAL_NETWORK=proxy-ssl
      - VIRTUAL_PORT=80
      - MYSQL_DATABASE=dbnext
      - MYSQL_USER=nextclouduser
      - MYSQL_PASSWORD=azdqsdokpokpokds098098
      - MYSQL_HOST=databasenext
      - REDIS_HOST=redisnext
      - REDIS_HOST_PASSWORD=aeazddokpokqsd7676
    ports:
      - "127.0.0.1:8889:80"
    volumes:
      - ncdata-data:/data
      - ncdata-config:/var/www/html/config
      - ncdata-apps:/var/www/html/custom_apps
      # - ./Internal.php:/var/www/html/lib/private/Session/Internal.php 
    networks:
      - default
      - back

  databasenext:
    image: mariadb
    restart: always
    environment:
      - MYSQL_ROOT_PASSWORD=DZAJpokpdzaDOPA56667
      - MYSQL_USER=nextclouduser
      - MYSQL_DATABASE=dbnext
      - MYSQL_PASSWORD=azdqsdokpokpokds098098
      #- MYSQL_INITDB_SKIP_TZINFO=1
    volumes:
      - ncdata-mysql:/var/lib/mysql
    networks:
      - back

  redisnext:
    image: redis
    networks:
      - back
    volumes:
      - ./redis.conf:/usr/local/etc/redis/redis.conf
    command: redis-server /usr/local/etc/redis/redis.conf

  coturn:
    image: instrumentisto/coturn
    container_name: nextcloud-coturn
    restart: unless-stopped
    ports:
      - "3478:3478/tcp"
      - "3478:3478/udp"
    networks:
      - back
    command:
      - -n
      - --log-file=stdout
      - --min-port=49160
      - --max-port=49200
      - --realm=nextcloud.mondomaine.fr
      - --use-auth-secret
      - --static-auth-secret=mysecreteasy
      - --cert=/etc/letsencrypt/live/nextcloud.mondomaine.fr/fullchain.pem
      - --pkey=/etc/letsencrypt/live/nextcloud.mondomaine.fr/privkey.pem

  collabora:
    image: collabora/code
    container_name: nextcloud-collabora
    restart: unless-stopped
    extra_hosts:
      - nextcloud.mondomaine.fr:217.70.188.81
      - collabora.mondomaine.fr:217.70.188.81
    networks:
      - back
    ports:
      - 127.0.0.1:9980:9980
    environment:
      - domain=nextcloud.mondomaine.fr
      - dictionaries=fr,en
    cap_add:
      - MKNOD
    tty: true

  paddb:
    restart: always
    image: "postgres:10"
    environment:
      - POSTGRES_DB=etherpad
      - POSTGRES_USER=etherpad
      - POSTGRES_PASSWORD=changeme
    networks:
      - app

  etherpad:
    build:
      context: ./etherpad-lite
    ports:
      - 9001:9001
    networks:
      - app

networks:
  back:
  app:

volumes:
  ncdata-mysql:
    driver_opts:
      type: none
      device: /data/nextcloud/mysql
      o: bind
  ncdata-data:
    driver_opts:
      type: none
      device: /data/nextcloud/data
      o: bind
  ncdata-config:
    driver_opts:
      type: none
      device: /data/nextcloud/config
      o: bind
  ncdata-apps:
    driver_opts:
      type: none
      device: /data/nextcloud/apps
      o: bind

Il ne reste plus qu'à démarrer les conteneurs:

docker-compose up --build -d

5. Installation de SSL pour Nginx avec Let's Encrypt

Let's encrypt est un service qui permet de générer gratuitement un certificat SSL pour le domaine de notre choix.

sudo apt install python3-acme python3-certbot python3-mock python3-openssl python3-pkg-resources python3-pyparsing python3-zope.interface
sudo apt install python3-certbot-nginx
sudo certbot certonly -d nextcloud.mondomaine.fr 
sudo certbot certonly -d collabora.mondomaine.fr
sudo certbot certonly -d pad.mondomaine.fr

Les certificats sont alors crés dans /etc/letsencrypt/nextcloud.mondomaine.fr!

6. Configuration du **Server Bloc Nginx ** pour nextcloud.mondomaine.fr

On peut maintenant créer le fichier de configuration nginx dans /etc/nginx/sites-available/nextcloud.mondomaine.fr.conf:

nextcloud.mondomaine.fr.conf
upstream nextcloud{
  server 127.0.0.1:8889;
}

server {
    if ($host = nextcloud.mondomaine.fr) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


    listen 80;
    server_name nextcloud.mondomaine.fr;
    location /.well-known {
            alias /var/www/nextcloud.mondomaine.fr/.well-known;
    }
    location / {
    rewrite ^/(.*)$  https://$host/$1 permanent;
  }
}

server {
  listen 443 ssl;
  server_name nextcloud.mondomaine.fr;
  proxy_read_timeout 720s;
  proxy_connect_timeout 720s;
  proxy_send_timeout 720s;

  proxy_set_header X-Forwarded-Host $host;
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  proxy_set_header X-Forwarded-Proto $scheme;
  proxy_set_header X-Real-IP $remote_addr;

  proxy_set_header Host $host;

  add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

  # Upload limit and security
  client_max_body_size 10000m;
  server_tokens off;

  # SSL parameters
  ssl on;
    ssl_certificate /etc/letsencrypt/live/nextcloud.mondomaine.fr/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/nextcloud.mondomaine.fr/privkey.pem; # managed by Certbot
  ssl_session_timeout 30m;
  ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
# ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';
# ssl_prefer_server_ciphers on;

  # log
  access_log /var/log/nginx/nextcloud.access.log;
  error_log /var/log/nginx/nextcloud.error.log;

  # Redirect requests to nextcloud backend server
  location / {
    proxy_redirect off;
    proxy_pass http://nextcloud;
  }
  rewrite ^/.well-known/webfinger /public.php?service=webfinger last;
  location /.well-known/carddav {
      return 301 $scheme://$host/remote.php/dav;
    }
  location /.well-known/caldav {
      return 301 $scheme://$host/remote.php/dav;
    }
  location /.well-known/acme-challenge {
        alias /var/www/nextcloud.mondomaine.fr/.well-known/acme-challenge;
    }

#  location ^/(.*\.php(/.*)?$ {
#    proxy_pass fcgi://127.0.0.1:6379/var/www/html/$1;
#  }
  # common gzip
  gzip_types text/css text/less text/plain text/xml application/xml application/json application/javascript;
  gzip on;
}

Pour activer ce fichier de configuration, on créé un lien symbolique vers le dossier sites-enabled:

sudo ln -s /etc/nginx/sites-available/wordpress.mondomaine.fr.conf /etc/nginx/sites-enabled/

7. Configuration du Server Bloc Nginx pour pad.mondomaine.fr

On peut maintenant créer le fichier de configuration nginx dans /etc/nginx/sites-available/pad.mondomaine.fr.conf:

pad.mondomaine.fr.conf

```
upstream pad { server 127.0.0.1:9001; }

server { server_name pad.mondomaine.fr listen [::]:443 ssl ipv6only=on; # managed by Certbot listen 443 ssl; # managed by Certbot ssl_certificate /etc/letsencrypt/live/pad.mondomaine.fr/fullchain.pem; # managed by Certbot ssl_certificate_key /etc/letsencrypt/live/pad.mondomaine.fr/privkey.pem; # managed by Certbot include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

location / {
    proxy_pass         http://pad;
    proxy_redirect     off;
    proxy_set_header   Host $host;
    proxy_set_header   X-Real-IP $remote_addr;
    proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header   X-Forwarded-Host $server_name;
    proxy_set_header   X-Forwarded-Proto https;
}

}

server { if ($host = pad.mondomaine.fr) { return 301 https://$host$request_uri; } # managed by Certbot location / { rewrite ^/(.*)$ https://$host/$1 permanent; }

    listen 80;
    listen [::]:80;

    server_name pad.mondomaine.fr;
return 404; # managed by Certbot

}

```

Pour activer ce fichier de configuration, on créé un lien symbolique vers le dossier sites-enabled:

sudo ln -s /etc/nginx/sites-available/pad.mondomaine.fr.conf /etc/nginx/sites-enabled/

Configuration du Server Bloc Nginx pour collabora.mondomaine.fr

On peut maintenant créer le fichier de configuration nginx dans /etc/nginx/sites-available/collabora.mondomaine.fr.conf:

collabora.mondomaine.fr.conf
upstream collabora{
  server 127.0.0.1:9980;
}

server {
    listen       443 ssl;
    server_name  collabora.mondomaine.fr;

    ssl on;
    ssl_certificate /etc/letsencrypt/live/collabora.mondomaine.fr/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/collabora.mondomaine.fr/privkey.pem;

    # static files
    location ^~ /loleaflet {
        proxy_pass https://localhost:9980;
        proxy_set_header Host $http_host;
    }

    # WOPI discovery URL
    location ^~ /hosting/discovery {
        proxy_pass https://localhost:9980;
        proxy_set_header Host $http_host;
    }

    # Capabilities
    location ^~ /hosting/capabilities {
        proxy_pass https://localhost:9980;
        proxy_set_header Host $http_host;
    }

    # main websocket
    location ~ ^/lool/(.*)/ws$ {
        proxy_pass https://localhost:9980;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
        proxy_set_header Host $http_host;
        proxy_read_timeout 36000s;
    }

    # download, presentation and image upload
    location ~ ^/lool {
        proxy_pass https://localhost:9980;
        proxy_set_header Host $http_host;
    }

    # Admin Console websocket
    location ^~ /lool/adminws {
        proxy_pass https://localhost:9980;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
        proxy_set_header Host $http_host;
        proxy_read_timeout 36000s;
    }
}

Pour activer ce fichier de configuration, on créé un lien symbolique vers le dossier sites-enabled:

sudo ln -s /etc/nginx/sites-available/collabora.mondomaine.fr.conf /etc/nginx/sites-enabled/

On recharge la configuration de Nginx:

sudo systemctl reload nginx.service

Et voilà, votre site est accessible à https://nextcloud.mondomaine.fr !

Plus d'infos sur la configuration de Nginx

Plus d'infos sur le guide Nginx de Linode

Configuration des applications dans Nextcloud

Configuration de etherpad

Il est nécessaire de récupérer la API KEY générée par etherpad: docker-compose exec pad bash cat APIKEY.txt

On la saisit ensuite dans Nextcloud: Settings > Administration > Additional settings

Mise à jour de l'image Docker Nextcloud

Vérifier la version en production

Avant chaque update checker dans les paramètres de base si des indices de table bdd manques ou autre.

L'update s'éffectue par palier de versions (20 vers 21 puis 21 vers 22, etc...)

étapes

  • stopper la stack:
docker-compose down
  • editer le tag de l'image Nextcloud dans le docker-compose.yaml:
nano docker-compose.yml
  • Modifier la version d'image vers la version immédiatement supérieure dans le docker-compose.yml

    Exemple: 20 -> 21

  • relançer la stack:

docker-compose up -d

Finir l'update via l'interface web

Une fois la mise à jour des applications éffectuée, se connecter et vérifier avec un compte administrateur dans Paramètres -> Vue d'ensemble si certaines tables de la base de donnée ont besoin d'être modifié via une commande explicitement décrite:

Exemple:

docker exec --user www-data -it nextcloud /var/www/html/occ db:add-missing-indices
Commande générique pour ajouter des colonnes manquantes à certaines tables de la BDD
docker exec --user www-data -it <NOM_DU_CONTAINER> /var/www/html/occ <COMMANDE-DB>
Crontab (pour nettoyer Nextcloud régulierement)
crontab -e
*/5  *  *  *  * docker exec -u www-data <NOM_DU_CONTAINER> php -f cron.php