抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >

Exodus

More than ideas.

手把手带你从零开始搭建起属于自己的NextCloud。

2020.10.18

初稿

2020.11.24

从脚本迁移到docker-compose部署,换用MariaDB

2021.03.30

增加acme.sh自动接管SSL

本文提供的NextCloud部署思路特点如下:

利用docker-compose实现一键部署/启动

易于维护,方便后续升级/插件添加

高度定制化,可根据需要自行更换DB/网页代理

需要占用本机80和443端口,难以与非docker部署的其他应用共存

出现故障后不容易确定问题,排障比较困难

请保证你的机器上可以正常使用docker和docker-compose。不同发行版的docker安装教程请自行查询。

Part 1 docker-compose配置

我们将使用docker-compose.yml文件来管理NextCloud及其配套组件的部署。

为了使NextCloud正常工作,我们至少需要两部分的组件:前端处理请求的Nginx和应用本体NextCloud。考虑到NextCloud是一个依赖于数据库的存储平台,我们最好选择一个相匹配的数据库。NextCloud官方推荐Mysql,但在此我选择了MariaDB。

综合上述考虑,我们需要至少三个docker image:Nginx、NextCloud和MariaDB。需要特别注意的是,NextCloud本身基于PHP,为了与Nginx配合并做到开箱即用,我们需要拉取特定的镜像,即集成了fpm的NextCloud。

虽然在下面给出的实例代码中,我拉取了stable-fpm版本的NextCloud,但还是建议根据实际情况选择适合的版本。目前我正在使用20.0.0的稳定版。Nginx和MariaDB的镜像版本选择同理。

下面我们来考虑docker-compose文件的某些细节。

首先是目录的映射问题。为了便于后续使用,NextCloud需要至少映射出两个目录:/var/www/config/var/www/html/custom_apps。前者便于修改NextCloud的相关配置;后者用于添加自定义的插件程序。在使用Nginx作为网页服务器的情况下,应该注意将/var/www/html映射到同一个位置。Nginx的配置文件在容器中/etc/nginx/conf.d下,可根据需要映射;为了便于自行配置SSL还可以映射/etc/nginx/ssl_certs

为了方便配置,在docker-compose文件中可以使用link直接连接我们的容器。同时在之后Nginx配置文件的书写中,我们也要考虑到这一点。

最后是环境变量问题。建议提前在docker-compose文件中就配置好数据库。数据库的环境变量可以参考下方的配置文件。其中用户名必须为NextCloud。

准备完毕后,在目录下创建docker-compose.yml文件,使用sudo docker-compose up -d就可以拉取镜像。

version: '3'

services:
  mysql:
    image: mariadb:10
    container_name: nextcloud-db
    restart: always
    volumes: 
      - /opt/NextCloud/db:/var/lib/mysql
      environment:
        - MYSQL_ROOT_PASSWORD=root
        - MYSQL_DATABASE=nextcloud
        - MYSQL_USER=nextcloud
        - MYSQL_PASSWORD=nextcloud
    networks:
      - nextcloud-network
  nextcloud:
    image: nextcloud:stable-fpm
    container_name: nextcloud
      restart: always
      volumes:
        - /opt/NextCloud/app/html:/var/www/html
        - /opt/NextCloud/app/custom_apps:/var/www/html/custom_apps
        - /opt/NextCloud/app/config:/var/www/html/config
        - /opt/NextCloud/app/data:/var/www/html/data
    links:
        - mysql
    ports:
        - 1080:80
    networks:
      - nextcloud-network
  nginx:
    image: nginx
    container_name: nextcloud-proxy
      restart: always
      volumes:
        - /opt/NextCloud/web/conf.d:/etc/nginx/conf.d
        - /opt/NextCloud/web/ssl_certs:/etc/nginx/ssl_certs
        - /opt/NextCloud/app/html:/var/www/html
      links:
        - mysql
        - nextcloud
      ports:
        - 80:80
        - 443:443
    networks:
      - nextcloud-network

networks:
  nextcloud-network:
    driver: bridge

注意在启动服务时,要确保80和443端口不被占用,否则Nginx容器会启动失败。可以用netstat -tunlp | grep 80来查看占用端口的程序。

Part 2 Nginx配置

镜像拉取,服务也成功启动,但是现在尝试访问网页应该还是失败的。问题很简单,还没有配置Nginx的代理设置。有兴趣折腾的同学可以直接参考官网的配置。

当然,以下也给出了一份可用的Nginx配置文件。只需将其略作修改,放置到之前配置docker-compose时映射的Nginx配置文件目录即可。

upstream php-handler {
    # 此处改为服务名
    server nextcloud:9000;
    #server unix:/var/run/php/php7.2-fpm.sock;
}

# 注意修改下方的域名和证书配置

server {
    listen 80;
    listen [::]:80;
    server_name cloud.example.com;
    # enforce https
    return 301 https://$server_name:443$request_uri;
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name cloud.example.com;

    # Use Mozilla's guidelines for SSL/TLS settings
    # https://mozilla.github.io/server-side-tls/ssl-config-generator/
    # NOTE: some settings below might be redundant
    ssl_certificate /etc/ssl/nginx/cloud.example.com.crt;
    ssl_certificate_key /etc/ssl/nginx/cloud.example.com.key;

    # Add headers to serve security related headers
    # Before enabling Strict-Transport-Security headers please read into this
    # topic first.
    #add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload;" always;
    #
    # WARNING: Only add the preload option once you read about
    # the consequences in https://hstspreload.org/. This option
    # will add the domain to a hardcoded list that is shipped
    # in all major browsers and getting removed from this list
    # could take several months.
    add_header Referrer-Policy "no-referrer" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-Download-Options "noopen" always;
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Permitted-Cross-Domain-Policies "none" always;
    add_header X-Robots-Tag "none" always;
    add_header X-XSS-Protection "1; mode=block" always;

    # Remove X-Powered-By, which is an information leak
    fastcgi_hide_header X-Powered-By;

    # Path to the root of your installation
      # 注意修改目录为NextCloud/app/html
    root /var/www/html;

    location = /robots.txt {
        allow all;
        log_not_found off;
        access_log off;
    }

    # The following 2 rules are only needed for the user_webfinger app.
    # Uncomment it if you're planning to use this app.
    #rewrite ^/.well-known/host-meta /public.php?service=host-meta last;
    #rewrite ^/.well-known/host-meta.json /public.php?service=host-meta-json last;

    # The following rule is only needed for the Social app.
    # Uncomment it if you're planning to use this app.
    #rewrite ^/.well-known/webfinger /public.php?service=webfinger last;

    location = /.well-known/carddav {
      return 301 $scheme://$host:$server_port/remote.php/dav;
    }
    location = /.well-known/caldav {
      return 301 $scheme://$host:$server_port/remote.php/dav;
    }

    # set max upload size
    client_max_body_size 512M;
    fastcgi_buffers 64 4K;

    # Enable gzip but do not remove ETag headers
    gzip on;
    gzip_vary on;
    gzip_comp_level 4;
    gzip_min_length 256;
    gzip_proxied expired no-cache no-store private no_last_modified no_etag auth;
    gzip_types application/atom+xml application/javascript application/json application/ld+json application/manifest+json application/rss+xml application/vnd.geo+json application/vnd.ms-fontobject application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/bmp image/svg+xml image/x-icon text/cache-manifest text/css text/plain text/vcard text/vnd.rim.location.xloc text/vtt text/x-component text/x-cross-domain-policy;

    # Uncomment if your server is build with the ngx_pagespeed module
    # This module is currently not supported.
    #pagespeed off;

    location / {
        rewrite ^ /index.php;
    }

    location ~ ^\/(?:build|tests|config|lib|3rdparty|templates|data)\/ {
        deny all;
    }
    location ~ ^\/(?:\.|autotest|occ|issue|indie|db_|console) {
        deny all;
    }

    location ~ ^\/(?:index|remote|public|cron|core\/ajax\/update|status|ocs\/v[12]|updater\/.+|oc[ms]-provider\/.+|.+\/richdocumentscode\/proxy)\.php(?:$|\/) {
        fastcgi_split_path_info ^(.+?\.php)(\/.*|)$;
        set $path_info $fastcgi_path_info;
        try_files $fastcgi_script_name =404;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $path_info;
        fastcgi_param HTTPS on;
        # Avoid sending the security headers twice
        fastcgi_param modHeadersAvailable true;
        # Enable pretty urls
        fastcgi_param front_controller_active true;
        fastcgi_pass php-handler;
        fastcgi_intercept_errors on;
        fastcgi_request_buffering off;
    }

    location ~ ^\/(?:updater|oc[ms]-provider)(?:$|\/) {
        try_files $uri/ =404;
        index index.php;
    }

    # Adding the cache control header for js, css and map files
    # Make sure it is BELOW the PHP block
    location ~ \.(?:css|js|woff2?|svg|gif|map)$ {
        try_files $uri /index.php$request_uri;
        add_header Cache-Control "public, max-age=15778463";
        # Add headers to serve security related headers (It is intended to
        # have those duplicated to the ones above)
        # Before enabling Strict-Transport-Security headers please read into
        # this topic first.
        #add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload;" always;
        #
        # WARNING: Only add the preload option once you read about
        # the consequences in https://hstspreload.org/. This option
        # will add the domain to a hardcoded list that is shipped
        # in all major browsers and getting removed from this list
        # could take several months.
        add_header Referrer-Policy "no-referrer" always;
        add_header X-Content-Type-Options "nosniff" always;
        add_header X-Download-Options "noopen" always;
        add_header X-Frame-Options "SAMEORIGIN" always;
        add_header X-Permitted-Cross-Domain-Policies "none" always;
        add_header X-Robots-Tag "none" always;
        add_header X-XSS-Protection "1; mode=block" always;

        # Optional: Don't log access to assets
        access_log off;
    }

    location ~ \.(?:png|html|ttf|ico|jpg|jpeg|bcmap|mp4|webm)$ {
        try_files $uri /index.php$request_uri;
        # Optional: Don't log access to other assets
        access_log off;
    }
}

上面的配置文件有几个需要注意的点。首先不要忘了将server_name修改为自己的地址;在配置文件的php-handler中,我们需要告诉Nginx接管php的服务地址。这里注意,配置中[ip:port],如果之前是按照本文思路配置,则ip的位置应为NextCloud相应的service名。最后,不要忘记SSL证书的问题。在下一节我们将给出两种配置证书的思路。

Part 3 SSL配置

首先我们介绍一种不仅可用于此次布置的SSL证书申请工具,Crypt-LE。

安装和使用都可以参考项目主页。在此仅提供一个用于泛域名激活的脚本。脚本采用DNS Challenge的方式完成认证。

#!/bin/bash
#Nginx使用.crt和.csr.key作为证书和私钥

domain="YOUR_DOMAIN_HERE"

le.pl\
    --generate-missing\
    --email YOUR_EMAIL_HERE\
    --key $domain.key\
    --csr $domain.csr\
    --csr-key $domain.csr.key\
    --crt $domain.crt\
    --domains "*.$domain, $domain"\
    --handle-as dns\
    --live

用此程序申请、维护SSL证书灵活但是相对繁琐,每90天需要手动更新一次。因此我们考虑另一种方法,使用acme.sh的docker镜像来帮助我们自动更新SSL。

我们使用acme.sh官方打包的镜像neilpang/acme.sh。为了方便管理,我们将其也加入到docker-compose文件中,于是最终得到的一份docker-compose.yml大致如下。

version: '3'

services:
  mysql:
    image: mariadb:10
    container_name: nextcloud-db
    restart: always
    volumes: 
      - /opt/NextCloud/db:/var/lib/mysql
    environment:
      - MYSQL_ROOT_PASSWORD=root
      - MYSQL_DATABASE=nextcloud
      - MYSQL_USER=nextcloud
      - MYSQL_PASSWORD=nextcloud
    networks:
      - nextcloud-network
  nextcloud:
    image: nextcloud:stable-fpm
    container_name: nextcloud
    restart: always
    volumes:
      - /opt/NextCloud/app/html:/var/www/html
      - /opt/NextCloud/app/custom_apps:/var/www/html/custom_apps
      - /opt/NextCloud/app/config:/var/www/html/config
      - /opt/NextCloud/app/data:/var/www/html/data
    links:
      - mysql
    networks:
      - nextcloud-network
  nginx:
    image: nginx
    container_name: nextcloud-proxy
    restart: always
    volumes:
      - /opt/NextCloud/web/conf.d:/etc/nginx/conf.d
      - /opt/NextCloud/web/ssl_certs:/etc/nginx/ssl_certs
      - /opt/NextCloud/app/html:/var/www/html
    links:
      - nextcloud
    ports:
      - 80:80
      - 443:443
    networks:
      - nextcloud-network
    labels:
      - sh.acme.autoload.domain=cloud.example.com
  acme.sh:
    image: neilpang/acme.sh
    container_name: acme.sh
    restart: always
    command: daemon
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - /opt/NextCloud/acmeout:/acme.sh
      - /opt/NextCloud/web/conf.d:/etc/nginx/conf.d
      - /opt/NextCloud/web/ssl_certs:/etc/nginx/ssl_certs
      - /opt/NextCloud/app/html:/var/www/html
    links:
      - nginx
    environment:
      - DEPLOY_DOCKER_CONTAINER_LABEL=sh.acme.autoload.domain=cloud.example.com
      - DEPLOY_DOCKER_CONTAINER_KEY_FILE=/etc/nginx/ssl_certs/cloud.example.com.key
      - DEPLOY_DOCKER_CONTAINER_CERT_FILE=/etc/nginx/ssl_certs/cloud.example.com.cer
      - DEPLOY_DOCKER_CONTAINER_CA_FILE=/etc/nginx/ssl_certs/ca.cer
      - DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE=/etc/nginx/ssl_certs/fullchain.cer
      - DEPLOY_DOCKER_CONTAINER_RELOAD_CMD="service nginx force-reload"
    networks:
      - nextcloud-network

networks:
  nextcloud-network:
    driver: bridge

上面给出的acme.sh配置是参考acme.sh官方wiki给出的,但实际上接下来会看到,仍然需要在使用中手动指定这些参数。

下面我们来解释一下acme.sh容器的工作流程。整个过程分两步:申请证书issue和部署证书deploy。

由于笔者使用阿里DNS,考虑到国内用户主要使用阿里DNS或腾讯云DNSPOD,因此我们申请证书使用自动DNS验证。以阿里云为例,在此处登录后,将其中的access-key和secret分别按官方说明作为参数传递给acme.sh,运行后acme.sh会自动完成申请证书的全过程。之后,只要整体配置不发生变化,运行的acme.sh容器也会自动再次申请证书,从而免去麻烦。

申请之后,我们再用acme.sh进行部署。我们会利用到其deploy-hook功能,这一功能使得通过特定的方法更新证书后,acme.sh可以自动按照给定的方法重新安装证书。申请和部署证书的脚本如下。

sudo docker exec \
    -e Ali_Key="YOUR_ACCESS_KEY_HERE" \
    -e Ali_Secret="YOUR_ACCESS_SECRET_HERE" \
    acme.sh --issue --dns dns_ali -d cloud.example.com 
sudo docker exec \
    -e DEPLOY_DOCKER_CONTAINER_LABEL=sh.acme.autoload.domain=cloud.example.com \
    -e DEPLOY_DOCKER_CONTAINER_KEY_FILE=/etc/nginx/ssl_certs/cloud.example.com.key \
    -e DEPLOY_DOCKER_CONTAINER_CERT_FILE="/etc/nginx/ssl_certs/cloud.example.com.cer" \
    -e DEPLOY_DOCKER_CONTAINER_CA_FILE="/etc/nginx/ssl_certs/ca.cer" \
    -e DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE="/etc/nginx/ssl_certs/fullchain.cer" \
    -e DEPLOY_DOCKER_CONTAINER_RELOAD_CMD="service nginx force-reload" \
    acme.sh --deploy -d cloud.example.com  --deploy-hook docker 

用acme.sh的方法部署时,需要保证nginx container是正常运行状态。因此正确的部署顺序应该是启动docker-compose -> 申请并部署证书 -> 启用nginx配置文件 -> 重启nginx;否则的话nginx会因为缺少SSL证书而无法正常运行,不能激活。

到此我们的NextCloud就可以正常运行啦,现在打开浏览器,不出意外的话就可以进入配置界面了。这篇文章也就此收尾。之后我会更新另一篇讲解NextCloud配置中常见问题的文章。

评论