Skip to content


NextCloud (a fork of OwnCloud, led by original developer Frank Karlitschek) is a suite of client-server software for creating and using file hosting services. It is functionally similar to Dropbox, although Nextcloud is free and open-source, allowing anyone to install and operate it on a private server.

NextCloud Screenshot

This recipe is based on the official NextCloud docker image, but includes seprate containers ofor the database (MariaDB), Redis (for transactional locking), Apache Solr (for full-text searching), automated database backup, (you do backup the stuff you care about, right?) and a separate cron container for running NextCloud's 15-min crons.



Already deployed:


  • Traefik Forward Auth to secure your Traefik-exposed services with an additional layer of authentication


Setup data locations

We'll need several directories for static data to bind-mount into our container, so create them in /var/data/nextcloud (so that they can be backed up)

mkdir /var/data/nextcloud
cd /var/data/nextcloud
mkdir -p {html,apps,config,data,database-dump}

Now make more directories for runtime data (so that they can be not backed-up):

mkdir /var/data/runtime/nextcloud
cd /var/data/runtime/nextcloud
mkdir -p {db,redis}

Prepare environment

Create nextcloud.env, and populate with the following variables


# For mysql
MYSQL_ROOT_PASSWORD=<set to something secure>
MYSQL_PASSWORD=set to something secure>

Now create a separate nextcloud-db-backup.env file, to capture the environment variables necessary to perform the backup. (If the same variables are shared with the mariadb container, they cause issues with database access)

# For database backup (keep 7 days daily backups)
MYSQL_PWD=<set to something secure, same as MYSQL_ROOT_PASSWORD above>

Setup Docker Swarm

Create a docker swarm config file in docker-compose syntax (v3), something like this:


I automatically and instantly share (with my sponsors) a private "premix" git repository, which includes necessary docker-compose and env files for all published recipes. This means that sponsors can launch any recipe with just a git pull and a docker stack deploy 👍.

🚀 Update: Premix now includes an ansible playbook, so that sponsors can deploy an entire stack + recipes, with a single ansible command! (more here)

version: "3.0"

    image: nextcloud
    env_file: /var/data/config/nextcloud/nextcloud.env
      - internal
      - traefik_public
          # traefik common
          - traefik.enable=true

          # traefikv1
          - traefik.port=80     

          # traefikv2
          - "traefik.http.routers.nextcloud.rule=Host(``)"
          - ""
          - "traefik.enable=true"

    - /var/data/nextcloud/html:/var/www/html
    - /var/data/nextcloud/apps:/var/www/html/custom_apps
    - /var/data/nextcloud/config:/var/www/html/config
    - /var/data/nextcloud/data:/var/www/html/data

    image: mariadb:10
    env_file: /var/data/config/nextcloud/nextcloud.env
      - internal
      - /var/data/runtime/nextcloud/db:/var/lib/mysql

    image: mariadb:10
    env_file: /var/data/config/nextcloud/nextcloud-db-backup.env
      - /var/data/nextcloud/database-dump:/dump
      - /etc/localtime:/etc/localtime:ro
    entrypoint: |
      bash -c 'bash -s <<EOF
      trap "break;exit" SIGHUP SIGINT SIGTERM
      sleep 2m
      while /bin/true; do
        mysqldump -h db --all-databases | gzip -c > /dump/dump_\`date +%d-%m-%Y"_"%H_%M_%S\`.sql.gz
        (ls -t /dump/dump*.sql.gz|head -n $$BACKUP_NUM_KEEP;ls /dump/dump*.sql.gz)|sort|uniq -u|xargs rm -- {}
        sleep $$BACKUP_FREQUENCY
    - internal

    image: redis:alpine
      - internal
      - /var/data/runtime/nextcloud/redis:/data

    image: nextcloud
      - /var/data/nextcloud/:/var/www/html
    user: www-data
      - internal
    entrypoint: |
      bash -c 'bash -s <<EOF
        trap "break;exit" SIGHUP SIGINT SIGTERM
        while [ ! -f /var/www/html/config/config.php ]; do
          sleep 1
        while true; do
          php -f /var/www/html/cron.php
          sleep 15m

    external: true
    driver: overlay
        - subnet:


Setup unique static subnets for every stack you deploy. This avoids IP/gateway conflicts which can otherwise occur when you're creating/removing stacks a lot. See my list here.


Launch NextCloud stack

Launch the NextCloud stack by running docker stack deploy nextcloud -c <path -to-docker-compose.yml>

Log into your new instance at https://YOUR-FQDN, with user "admin" and the password you specified in nextcloud.env.

Enable redis

To make NextCloud a little snappier, edit /var/data/nextcloud/config/config.php (now that it's been created on the first container launch), and add the following:

 'redis' => array(
     'host' => 'redis',
     'port' => 6379,

Use service discovery

Want to use Calendar/Contacts on your iOS device? Want to avoid dictating long, rambling URL strings to your users, like ?

Huzzah! NextCloud supports service discovery for CalDAV/CardDAV, allowing you to simply tell your device the primary URL of your server (, for example), and have the device figure out the correct WebDAV path to use.

We (and anyone else using the NextCloud Docker image) are using an SSL-terminating reverse proxy (Traefik) in front of our NextCloud container. In fact, it's not possible to setup SSL within the NextCloud container.

When using a reverse proxy, your device requests a URL from your proxy (, and the reverse proxy then passes that request unencrypted to the internal URL of the NextCloud instance (i.e.,

The Apache webserver on the NextCloud container (knowing it was spoken to via HTTP), responds with a 301 redirect to See the problem? You requested an HTTPS (encrypted) url, and in return, you received a redirect to an HTTP (unencrypted) URL. Any sensible client (iOS included) will refuse such schenanigans.

To correct this, we need to tell NextCloud to always redirect the .well-known URLs to an HTTPS location. This can only be done after deploying NextCloud, since it's only on first launch of the container that the .htaccess file is created in the first place.

To make NextCloud service discovery work with Traefik reverse proxy, edit /var/data/nextcloud/html/.htaccess, and change this:

RewriteRule ^\.well-known/carddav /remote.php/dav/ [R=301,L]
RewriteRule ^\.well-known/caldav /remote.php/dav/ [R=301,L]

To this:

RewriteRule ^\.well-known/carddav https://%{SERVER_NAME}/remote.php/dav/ [R=301,L]
RewriteRule ^\.well-known/caldav https://%{SERVER_NAME}/remote.php/dav/ [R=301,L]

Then restart your container with docker service update nextcloud_nextcloud --force to restart apache.

Your can test for success by running curl -i You should get a 301 redirect to your equivalent of, as below:

[davidy:~] % curl -i
HTTP/2 301
content-type: text/html; charset=iso-8859-1
date: Wed, 12 Dec 2018 08:30:11 GMT

Note that this .htaccess can be overwritten by NextCloud, and you may have to reapply the change in future. I've created an issue requesting a permanent fix.

Chef's notes 📓

  1. Since many of my other recipes use PostgreSQL, I'd have preferred to use Postgres over MariaDB, but MariaDB seems to be the preferred database type

  2. I'm not the first user to stumble across the service discovery bug with reverse proxies. 

Tip your waiter (sponsor) 👏

Did you receive excellent service? Want to compliment the chef? (..and support development of current and future recipes!) Sponsor me on Github / Patreon, or see the contribute page for more (free or paid) ways to say thank you! 👏

Employ your chef (engage) 🤝

Is this too much of a geeky PITA? Do you just want results, stat? I do this for a living - I provide consulting and engineering expertise to businesses needing short-term, short-notice support in the cloud-native space, including AWS/Azure/GKE, Kubernetes, CI/CD and automation.

Learn more about working with me here.

Flirt with waiter (subscribe) 💌

Want to know now when this recipe gets updated, or when future recipes are added? Subscribe to the RSS feed, or leave your email address below, and we'll keep you updated.

Notify me 🔔

Be the first to know when recipes are added / improved!

    We won't send you spam. Unsubscribe at any time. No monkey-business.

    Powered By ConvertKit

    Your comments? 💬

    Last update: October 27, 2021
    Back to top