NC17 with Traefik, docker-compose and PHP-FPM

Ok guys, not my first time with NC, but I’m horribly stuck.
Running the apache image of NC is working, however it seems to me redis is not working (NC is slow) and I find no way to change php options. The github-page mentions a nginx container so I ended up with this docker-compose.yml

version: '3'

services:
  traefik:
    hostname: traefik
    container_name: traefik
    image: traefik:v1.7.16-alpine
    restart: always
    networks:
      - external
      - internal
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ${USERDIR}/docker/traefik:/etc/traefik
      - ${USERDIR}/docker/traefik/acme:/acme
    labels:
      - "traefik.enable=true"
      - "traefik.port=8080"
      - "traefik.backend=traefik"
      - "traefik.frontend.rule=Host:traefik.${DOMAINNAME}"  
      - "traefik.docker.network=external"
      - "traefik.frontend.headers.SSLRedirect=true"
      - "traefik.frontend.headers.STSSeconds=315360000"
      - "traefik.frontend.headers.browserXSSFilter=true"
      - "traefik.frontend.headers.contentTypeNosniff=true"
      - "traefik.frontend.headers.forceSTSHeader=true"
      - "traefik.frontend.headers.SSLHost=${DOMAINNAME}"
      - "traefik.frontend.headers.STSIncludeSubdomains=true"
      - "traefik.frontend.headers.STSPreload=true"
      - "traefik.frontend.headers.frameDeny=true"
    command:
      --api
      --docker
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - CF_API_EMAIL=
      - CF_API_KEY=

  portainer:
    image: portainer/portainer
    container_name: portainer
    restart: always
    command: -H unix:///var/run/docker.sock
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ${USERDIR}/docker/portainer/data:/data
    environment:
      - TZ=${TZ}
    networks:
      - external
    labels:
      - "traefik.enable=true"
      - "traefik.backend=portainer"
      - "traefik.frontend.rule=Host:portainer.${DOMAINNAME}"  
      - "traefik.port=9000"
      - "traefik.docker.network=external"
      - "traefik.frontend.headers.SSLRedirect=true"
      - "traefik.frontend.headers.STSSeconds=315360000"
      - "traefik.frontend.headers.browserXSSFilter=true"
      - "traefik.frontend.headers.contentTypeNosniff=true"
      - "traefik.frontend.headers.forceSTSHeader=true"
      - "traefik.frontend.headers.SSLHost=${DOMAINNAME}"
      - "traefik.frontend.headers.STSIncludeSubdomains=true"
      - "traefik.frontend.headers.STSPreload=true"

  phpmyadmin:
    hostname: phpmyadmin
    container_name: phpmyadmin
    image: phpmyadmin/phpmyadmin
    restart: always
    links:
      - mariadb:db
    environment:
      - PMA_HOST=mariadb
      - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
    networks:
      - external
      - internal
    labels:
      - "traefik.enable=true"
      - "traefik.backend=pma"
      - "traefik.frontend.rule=Host:pma.${DOMAINNAME}"
      - "traefik.port=80"
      - "traefik.docker.network=external" 
      - "traefik.frontend.headers.SSLRedirect=true"
      - "traefik.frontend.headers.STSSeconds=315360000"
      - "traefik.frontend.headers.browserXSSFilter=true"
      - "traefik.frontend.headers.contentTypeNosniff=true"
      - "traefik.frontend.headers.forceSTSHeader=true"
      - "traefik.frontend.headers.SSLHost=${DOMAINNAME}"
      - "traefik.frontend.headers.STSIncludeSubdomains=true"
      - "traefik.frontend.headers.STSPreload=true"
      - "traefik.frontend.headers.frameDeny=true"

# MariaDB – Database Server for your Apps
  mariadb:
    image: "linuxserver/mariadb"
    container_name: "mariadb"
    hostname: mariadb
    restart: always
    ports:
      - 3307:3307
    environment:
      - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
    volumes:
      - ${USERDIR}/docker/mariadb:/config
    networks:
      - internal

  redis:
    image: redis:alpine
    hostname: redis
    container_name: redis
    restart: always
    networks:
      - internal

  nginx:
    image: nginx
    ports:
      - 8080:80
    volumes:
      - ${USERDIR}/docker/nginx.conf:/etc/nginx/nginx.conf:ro
      - /mnt/data-storage/nextcloud:/var/www/html
    restart: always
    labels:
      - "traefik.enable=true"
      - "traefik.backend=nginx"
      - "traefik.port=80"
      - "traefik.frontend.rule=Host:${DOMAINNAME}"
      - "traefik.docker.network=external"
    networks:
      - external
      - internal
 
# NextCloud – Your Own Cloud Storage
  nextcloud:
    image: nextcloud:fpm
    container_name: nextcloud
    hostname: nextcloud
    restart: always
    ports:
      - 80
    networks:
      - external
      - internal
    environment:
      PUID: ${PUID}
      PGID: ${PGID}
      MYSQL_DATABASE: ${MYSQL_NEXTCLOUD_DB}
      MYSQL_USER: ${MYSQL_NEXTCLOUD_USER}
      MYSQL_PASSWORD: ${MYSQL_NEXTCLOUD_PASSWORD}
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      NEXTCLOUD_ADMIN_USER: ${NEXTCLOUD_ADMIN_USER}
      NEXTCLOUD_ADMIN_PASSWORD: ${NEXTCLOUD_ADMIN_PASSWORD}
      MYSQL_HOST: mariadb
      REDIS_HOST: redis
    links:
      - mariadb
      - nginx
      - redis
    depends_on:
      - mariadb
      - redis
    volumes:
      - /mnt/data-storage/nextcloud:/var/www/html
#      - /mnt/data-storage/nextcloud/config:/var/www/html/config
#      - /mnt/data-storage/nextcloud/data:/var/www/html/data
#      - /mnt/data-storage/nextcloud/themes:/var/www/html/themes
    labels:
      - "traefik.enable=true"
      - "traefik.backend=nextcloud"
      - "traefik.port=80"
      - "traefik.frontend.rule=Host:cloud.${DOMAINNAME}"
      - "traefik.frontend.headers.SSLHost=${DOMAINNAME}"
      - "traefik.docker.network=external"
      - "traefik.frontend.headers.SSLRedirect=true"
      - "traefik.frontend.headers.browserXSSFilter=true"
      - "traefik.frontend.headers.contentTypeNosniff=true"
      - "traefik.frontend.headers.forceSTSHeader=true"
      - "traefik.frontend.headers.STSSeconds=315360000"
      - "traefik.frontend.headers.STSIncludeSubdomains=true"
      - "traefik.frontend.headers.STSPreload=true"
      - "traefik.frontend.headers.frameDeny=true"
      - "traefik.frontend.redirect.permanent=true"
      - "traefik.frontend.redirect.regex=https://(.*)/.well-known/(card|cal)dav"
      - "traefik.frontend.redirect.replacement=https://$$1/remote.php/dav/"
      - "traefik.frontend.headers.customFrameOptionsValue=allow-from https://cloud.${DOMAINNAME}"
      - "traefik.frontend.passHostHeader=true"

networks:
  external:
    external:
      name: external
  internal:
    driver: bridge

However opening the page gives a Bad Gateway.
Can someone help me pls with my docker-compose file so I get

  • Traefik as reverse proxy
  • nginx as webserver
  • NC with fully functional php-fpm, redis etc

Thanks in advance for your kind help!
Jared

you don’t need to expose the ports of the nginx and nextcloud container and both don’t need to be on the external network. only traefik is facing extern.

the nextcloud:fpm container has “traefik.enable=false” because he is only the upstream server for php file in nginx. in your nginx config should be something like

    upstream php-handler {
        server nextcloud:9000;
    }

and you have to move all the traefik labels from nextcloud:fpm to nginx.

and i would advice fpm-alpine, nginx-alpine, redis-alpine, and so on. smaller images, less packages to patch.

the rest you’ll find here (but in ansible notation, but you can easily translate into docker-compose.)

or

which i tried to ansiblelyse here

p.s.: if you figured out how to move to traefik 2.0 please share your config. i would be happy to migrate my playbooks too. :wink:

p.p.s.: performance junkies continue here: https://medium.com/@jonbaldie/how-to-connect-to-redis-with-unix-sockets-in-docker-9e94e01b7acd :sunglasses:
applies to db & fpm as well. i guess.

But if I move ALL labels to nginx, this would mean nginx can only serve to my nextcloud container, as the frontend.rule=Host:... would be available on nginx.

it should be like this:

external --> (port 80/443 )traefik --> (port 80) nginx --> (port 9000) nextcloud:fpm

it is not like this:

traefik  --> (port 80) nginx
         |-> (port 80) nextcloud:fpm

in this example it’s also like this. only the “./web” container is connected to the proxy tier. (they use nginx instead of traefik)

I’m sorry, I still don’t get it.

What I think I understood:

  • traefik accepts requests on 80/443
  • traefik sends those requests to nginx on 80
  • nginx get php-files from nextcloud-fpm on 9000

It says 404 page not found when connecting to https://cloud.example.com

version: '3'

services:
  traefik:
    hostname: traefik
    container_name: traefik
    image: traefik:v1.7.16-alpine
    restart: always
    networks:
      - external
      - internal
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ${USERDIR}/docker/traefik:/etc/traefik
      - ${USERDIR}/docker/traefik/acme:/acme
    labels:
      - "traefik.enable=true"
      - "traefik.port=8080"
      - "traefik.backend=traefik"
      - "traefik.frontend.rule=Host:traefik.${DOMAINNAME}"  
      - "traefik.docker.network=external"
      - "traefik.frontend.headers.SSLRedirect=true"
      - "traefik.frontend.headers.STSSeconds=315360000"
      - "traefik.frontend.headers.browserXSSFilter=true"
      - "traefik.frontend.headers.contentTypeNosniff=true"
      - "traefik.frontend.headers.forceSTSHeader=true"
      - "traefik.frontend.headers.SSLHost=${DOMAINNAME}"
      - "traefik.frontend.headers.STSIncludeSubdomains=true"
      - "traefik.frontend.headers.STSPreload=true"
      - "traefik.frontend.headers.frameDeny=true"
    command:
      --api
      --docker
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - CF_API_EMAIL=
      - CF_API_KEY=

  portainer:
    image: portainer/portainer
    container_name: portainer
    restart: always
    command: -H unix:///var/run/docker.sock
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ${USERDIR}/docker/portainer/data:/data
    environment:
      - TZ=${TZ}
    networks:
      - internal
    labels:
      - "traefik.backend=portainer"
      - "traefik.frontend.rule=Host:portainer.${DOMAINNAME}"  
      - "traefik.port=9000"
      - "traefik.docker.network=external"

  phpmyadmin:
    hostname: phpmyadmin
    container_name: phpmyadmin
    image: phpmyadmin/phpmyadmin
    restart: always
    links:
      - mariadb:db
    environment:
      - PMA_HOST=mariadb
      - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
    networks:
      - internal
    labels:
      - "traefik.backend=pma"
      - "traefik.frontend.rule=Host:pma.${DOMAINNAME}"
      - "traefik.port=80"
      - "traefik.docker.network=external" 

# MariaDB – Database Server for your Apps
  mariadb:
    image: "linuxserver/mariadb"
    container_name: "mariadb"
    hostname: mariadb
    restart: always
    ports:
      - 3307:3307
    environment:
      - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
    volumes:
      - ${USERDIR}/docker/mariadb:/config
    networks:
      - internal

  redis:
    image: redis:alpine
    hostname: redis
    container_name: redis
    restart: always
    networks:
      - internal

  nginx:
    image: nginx:alpine
    volumes:
      - ${USERDIR}/docker/nginx.conf:/etc/nginx/nginx.conf:ro
      - /mnt/data-storage/nextcloud:/var/www/html
    restart: always
    ports:
      - 80
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
    labels:
      - "traefik.backend=nginx"
      - "traefik.port=80"
      - "traefik.frontend.rule=Host:${DOMAINNAME}"
      - "traefik.frontend.headers.SSLHost=${DOMAINNAME}"
      - "traefik.frontend.headers.SSLRedirect=true"
      - "traefik.frontend.headers.browserXSSFilter=true"
      - "traefik.frontend.headers.contentTypeNosniff=true"
      - "traefik.frontend.headers.forceSTSHeader=true"
      - "traefik.frontend.headers.STSSeconds=315360000"
      - "traefik.frontend.headers.STSIncludeSubdomains=true"
      - "traefik.frontend.headers.STSPreload=true"
      - "traefik.frontend.headers.frameDeny=true"
      - "traefik.frontend.redirect.permanent=true"
      - "traefik.frontend.redirect.regex=https://(.*)/.well-known/(card|cal)dav"
      - "traefik.frontend.redirect.replacement=https://$$1/remote.php/dav/"
      - "traefik.frontend.headers.customFrameOptionsValue=allow-from https://cloud.${DOMAINNAME}"
      - "traefik.frontend.passHostHeader=true"
      - "traefik.docker.network=external"
    networks:
      - internal
 
# NextCloud – Your Own Cloud Storage
  nextcloud:
    image: nextcloud:fpm-alpine
    container_name: nextcloud
    hostname: nextcloud
    restart: always
    ports:
      - 80
      - 9000
    networks:
      - internal
    environment:
      PUID: ${PUID}
      PGID: ${PGID}
      MYSQL_DATABASE: ${MYSQL_NEXTCLOUD_DB}
      MYSQL_USER: ${MYSQL_NEXTCLOUD_USER}
      MYSQL_PASSWORD: ${MYSQL_NEXTCLOUD_PASSWORD}
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      NEXTCLOUD_ADMIN_USER: ${NEXTCLOUD_ADMIN_USER}
      NEXTCLOUD_ADMIN_PASSWORD: ${NEXTCLOUD_ADMIN_PASSWORD}
      MYSQL_HOST: mariadb
      REDIS_HOST: redis
    links:
      - mariadb
      - redis
    depends_on:
      - mariadb
      - redis
    volumes:
      - /mnt/data-storage/nextcloud:/var/www/html
    labels:
      - "traefik.enable=false"

networks:
  external:
    external:
      name: external
  internal:
    driver: bridge

worker_processes auto;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

#    set_real_ip_from  10.0.0.0/8;
#    set_real_ip_from  172.16.0.0/12;
#    set_real_ip_from  192.168.0.0/16;
#    real_ip_header    X-Real-IP;

    #gzip  on;

    upstream php-handler {
        server nextcloud:9000;
    }

    server {
        listen 80;

        # 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
        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 10G;
        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\/.+)\.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)$ {
            try_files $uri /index.php$request_uri;
            # Optional: Don't log access to other assets
            access_log off;
        }
    }
}


i guess that’s the traefik error message. not a nginx one.

${DOMAINNAME} is equal to what? “cloud.example.com” or “example.com”

if example.com is the value of ${DOMAINNAME} nginx has the frontend rule “example.com”

you don’t need to have subdomain for each service.
if you want to put traefik into a subfolder “/traefik”:

- traefik.frontend.rule=Host:${DOMAINNAME}; PathPrefixStrip:/traefik"

ports: exposes this container port to your host (and the outer network). normally you don’t want this. you installed phpmyadmin to administrate your db. only if you would like to use a mysql workbench on your pc/laptop you want to expose the db port to the world.

Read
https://techrevelations.de/2019/11/10/nextcloud-and-traefik-v2/