Running Nextcloud using Docker and Traefik 3

Nextcloud is an awesome self-hosted cloud-storage service. This is a guide for running Nextcloud using Docker, Traefik, and Let’s Encrypt.

Thumbnail of the blog post

Nextcloud is an awesome self-hosted cloud-storage service. This is a guide for running Nextcloud using Docker, Traefik, and Let’s Encrypt.

Why should you use Docker to deploy Nextcloud?

Well- Have you ever tried setting up a working Nextcloud instance just by using an Apache or NGINX web server? That itself probably isn’t that hard but the configuration you have to do after installing Nextcloud is. There’s this handy dandy feature under Settings -> [Administration] Overview that shows you what steps you need to take to make your Nextcloud instance secure, fast, and reliable. And if you’ve just set up Nextcloud- oh boy- there’s probably a lot to do.

Since Docker images not only come with the software installed already, but also with a finished and optimized configuration, there’s pretty much nothing more to do once you’ve got your Nextcloud container up and running.

Screenshot of my Nextcloud overview (using Docker)

Also, probably because Nextcloud is such complex software, I was never really able to optimize my manual Nextcloud instance as well as the Docker image. I always noticed performance issues or other kinds of things that just didn’t work or didn’t work as expected.

Why should you run the instance behind Traefik?

Traefik acts as the “reverse proxy” in this configuration. That means we don’t have to expose the web server in the container publicly on the web, which has a couple of advantages. One of them being that you don’t have to run it on a different port or something when dealing with several services on one server.

An example of that would be my server. I don’t just run my blog but also a homepage on that server, which are two completely separate web apps, code-bases, and Docker containers. I don’t have to use weird ports behind the web address though, because everything is routed through Traefik.

That enables me to tell Traefik to route a specific subdomain or path, like “blog.fabiancdng.com” or “fabiancdng.com/blog” to the blog container and “fabiancdng.com” to the homepage container.

I’m also able to easily rewrite HTTP headers, implement middlewares, and more.

Why specifically Traefik and not other reverse proxies (like NGINX or Apache)?

Well- that’s probably a question for a separate article. One of the reasons is certainly the way it works hand-in-hand with Docker and the fact that it needs almost no configuration (in the form of config files).

But you’ll see all of this yourself- now.

Step 1: Setting up Nextcloud, MariaDB, and Redis

Of course, you can also use MySQL instead of MariaDB.

Creating the docker-compose.yml for Nextcloud, MariaDB, and Redis:

First, we’ll set up a Docker Compose stack for Nextcloud, MariaDB, and Redis:

version: '3.7'
services:
  nextcloud-db:
    image: mariadb:latest
    container_name: nextcloud-db
    command: --transaction-isolation=READ-COMMITTED --log-bin=ROW --skip-innodb-read-only-compressed
    restart: unless-stopped
    volumes:
      - ./mysql-data:/var/lib/mysql
    environment:
      - MYSQL_ROOT_PASSWORD=test # MYSQL ROOT PASSWORD
      - MYSQL_PASSWORD=test # PASSWORD OF MYSQL USER
      - MYSQL_DATABASE=nextcloud # MYSQL DATABASE
      - MYSQL_USER=nextcloud # MYSQL USERNAME
    networks:
      - default

  nextcloud-redis:
    image: redis:alpine
    container_name: nextcloud-redis
    hostname: nextcloud-redis
    restart: unless-stopped
    command: redis-server --requirepass test # REPLACE WITH REDIS PASSWORD
    networks:
      - default

  nextcloud-app:
    image: nextcloud
    container_name: nextcloud-app
    restart: unless-stopped
    depends_on:
      - nextcloud-db
      - nextcloud-redis
    environment:
      - REDIS_HOST=nextcloud-redis
      - REDIS_HOST_PASSWORD=test # REPLACE WITH REDIS PASSWORD
      - MYSQL_HOST=nextcloud-db
      - MYSQL_USER=nextcloud # MYSQL USERNAME
      - MYSQL_PASSWORD=test # PASSWORD OF MYSQL USER
      - MYSQL_DATABASE=nextcloud # MYSQL DATABASE
    volumes:
      - ./nextcloud:/var/www/html
    labels:
      - 'traefik.http.routers.nextcloud.entrypoints=http'
      - 'traefik.http.routers.nextcloud.rule=Host(`cloud.example.com`)'
      - 'traefik.http.middlewares.https-redirect.redirectscheme.scheme=https'
      - 'traefik.http.routers.nextcloud.middlewares=nc-header,https-redirect'
      - 'traefik.http.routers.nextcloud-secure.entrypoints=https'
      - 'traefik.http.routers.nextcloud-secure.rule=Host(`cloud.example.com`)'
      - 'traefik.http.middlewares.nc-rep.redirectregex.regex=https://(.*)/.well-known/(card|cal)dav'
      - 'traefik.http.middlewares.nc-rep.redirectregex.replacement=https://$$1/remote.php/dav/'
      - 'traefik.http.middlewares.nc-rep.redirectregex.permanent=true'
      - 'traefik.http.middlewares.nc-header.headers.customFrameOptionsValue=SAMEORIGIN'
      - 'traefik.http.middlewares.nc-header.headers.customResponseHeaders.Strict-Transport-Security=15552000'
      - 'traefik.http.routers.nextcloud-secure.middlewares=nc-rep,nc-header'
      - 'traefik.http.routers.nextcloud-secure.tls=true'
      - 'traefik.http.routers.nextcloud-secure.tls.certresolver=letsencrypt'
      - 'traefik.http.routers.nextcloud-secure.service=nextcloud'
      - 'traefik.http.services.nextcloud.loadbalancer.server.port=80'
      - 'traefik.http.services.nextcloud.loadbalancer.passHostHeader=true'
    networks:
      - proxy
      - default

networks:
  proxy:
    external: true

Important: Replace “cloud.example.com” with the (sub)domain you want to run your cloud on and replace the passwords with secure ones (see comments).

I started with the Nextcloud, MariaDB, and Redis Docker Compose stack because some of you may have already set up Traefik and created a network for it. In that case, you can just connect the containers to it by changing “proxy” to your network name. If you haven’t created a Docker network for Traefik yet, don’t worry, we’re going to do that in Step 2.

There’s not much to explain here, except maybe what all those Traefik labels mean. Basically, there’s a lot to do when a request to your cloud domain comes in.

The first thing you want to do is upgrading the connection from HTTP to HTTPS. This is simply done by creating a middleware using the container labels (yes, that’s possible). Eventually, the incoming traffic gets routed to a “service”, which in this case is port 80 within the container.

You don’t additionally have to expose the port on the host machine. Since the Traefik container connects to the same Docker network as the Nextcloud container, they can communicate on that port (using that network).

Then there’s a bunch of middlewares for (over)writing and passing headers of requests (I just followed the instructions on the Nextcloud overview and the docs for optimizing those). See “Hardening and security guidance” for reference.

Step 2: Setting up Traefik

The first thing that’s important, as mentioned above, is creating a Docker network that all the containers that you want to run behind Traefik connect to. This is done so we can route the traffic internally as opposed to exposing ports on the host machine.

Creating the network

You can create a new Docker network using:

$ docker network create proxy

You can name it anything you want. For consistency with my example stack above, I call it “proxy”.

Creating the docker-compose.yml for Traefik

version: '3.7'

services:
  traefik:
    image: traefik:latest
    container_name: traefik
    restart: unless-stopped
    ports:
      - '80:80'
      - '443:443'
    volumes:
      - ./traefik/config/traefik.yml:/traefik.yml
      - ./traefik/certificates/acme.json:/acme.json
      - /var/run/docker.sock:/var/run/docker.sock
    networks:
      - proxy

networks:
  proxy:
    external: true

For Traefik we obviously have to expose the ports 80 and 443 to the host machine to make the HTTP and HTTPS traffic that comes in on the host machine go straight to the Traefik container.

Also, we have to create 2 volumes for a) storing a minimal Traefik config file and b) storing our SSL certificates. We’ll later set up Let’s encrypt (in the Traefik config file) to obtain free SSL certificates for our Nextcloud, which is recommended if we want to use HTTPS.

Step 3: Configuring Traefik

Last but not least, we have to create our configuration file for Traefik. Technically this is not necessary and we could do everything using labels. But especially for people who want to put more behind Traefik than just one service, this can make things cleaner and a lot easier to read.

Start by creating a folder called traefik in the same directory as the docker-compose.yml (for Traefik) and a subdirectory within that folder called config then create a file called traefik.yml and paste the following:

api:
  dashboard: false
  insecure: false

entryPoints:
  http:
    address: ':80'
  https:
    address: ':443'

providers:
  docker:
    network: proxy

certificatesResolvers:
  letsencrypt:
    acme:
      email: your@email
      storage: acme.json
      httpChallenge:
        entryPoint: http

This just creates our certificate resolver, which is automatically going to obtain and renew the certificates. It also deactivates the internal dashboard (if you want to enable it though, feel free to do so).

If it doesn’t work, make sure the certificates folder has the permissions 600. Change the permissions by doing chmod -R 600 certificates/.

Done! 🥳

Both docker-compose.yml files have to be in different folders for Docker Compose to use the correct one. Start both of them (separately) by running docker-compose up -d in both directories.

You can now open up your browser and go to the domain of your Nextcloud. You should be greeted with the initial setup screen.

Cheers.