Docker: Docker compose example fpm nginx explanation and more apps

Created on 20 Sep 2018  路  9Comments  路  Source: nextcloud/docker

Hey there!
thanks for all the open source software you are providing here!

i have some questions about the docker compose examples you offer here.

on the ninx mariadb letsencrypt fpm example compose file, as long i understood it correct, there is nginx (offical) and jwilder/nginx installed with jwilder bound to the ports 80 and 443.

the offical nginx container is started with the nginx.config for the nextcloud server listening and they share the nextcloud html folder.
both nginx containers are on the same network (proxy-tier) but the folders are not the same and there are no bindings of the same directorys (certs, html etc)

so how is it working exactly?
my thoughts:
nexcloud is started with default port 9000 in default network, nginx offical is started with default settings on networks default and proxy-trier with a config file for nextcloud.
jwilder nginx is started with bounded ports and gonna create a server entry for the nginx offical which should expose ports 80/443 internaly taking the servername from virtual_host of the web build.

is this correct so far?
why is it done like this and would there be a way to use only jwilder nginx?

i am working on a docker setup where i can reach several services (pihole, nextcloud, portainer, openhab etc.) with my nginx reverse proxy.

to do so i would adjust the variables (folders etc.) in the provided compose file and just bind my other containers to the jwilder network (in this case proxy-trier) and it should work?

I hope this and the following postings are helpful for other users as well - took me some time to get into the docker stuff

help wanted

Most helpful comment

The example you're talking about requires an additional (plain) nginx server, because the php-fpm container, can't serve basic files like images. It's just for php requests and uses port 9000 (this is NOT http!!!). That's why the fpm and nginx container share the nextcloud volume. It also acts as proxy for the php requests and listens on it's http port.

Up to here you have a basic, unencrypted installation (port 80 is exposed by default).

The nginx-proxy is acting as an additional layer on top of the nextcloud-fpm-setup. It listens to the docker daemon and automatically rewrites it's rules to proxy any request to other docker containers, that you have configured accordingly. That's what the environment variables VIRTUAL_HOSTand VIRTUAL_PORT are for. With this setup you get the ability to serve different website on the same port on your host (80 for http and 443 for https) with different domain names and / or subdomains.
Internally the communication between proxy and nextcloud container is unencrypted http (port 80).

With the letsencrypt companion container you also get automatic generation and renewal of letsencrypt certificates, and the proxy will be configured to redirect to https.

If you want to run multiple services on your host, you can just add them to the docker-compose file, or create different compose files, for each service.

For example my setup looks like this:

/docker/
  -> proxy/docke-compose.yml
  -> nextcloud/docker-compose.yml
  -> portainer/docker-compose.yml
  -> wordpress/docker-compose.yml
  -> wordpress2/docker-compose.yml
  -> ldap/docker-compose.yml
  ...

The proxy part just contains the nginx proxy and the letsencrypt companion, as well as a user defined network.

version: '2'

volumes:
  conf.d:
  vhost.d:
  html:
  certs:


services:
  proxy:
    image: jwilder/nginx-proxy:alpine
    ports:
      - 80:80
      - 443:443
    volumes:
      - conf.d:/etc/nginx/conf.d
      - vhost.d:/etc/nginx/vhost.d
      - html:/usr/share/nginx/html
      - certs:/etc/nginx/certs:ro
      - /var/run/docker.sock:/tmp/docker.sock:ro
      - ./uploadsize.conf:/etc/nginx/conf.d/uploadsize.conf:ro
    networks:
      - proxy-tier
    restart: always
    labels:
      - com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy

  letsencrypt:
    image: jrcs/letsencrypt-nginx-proxy-companion
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - certs:/etc/nginx/certs:rw
      - conf.d:/etc/nginx/conf.d:ro
      - html:/usr/share/nginx/html:rw
      - vhost.d:/etc/nginx/vhost.d
    restart: always

networks:
  proxy-tier:

For nextcloud you would just have the database, php-fpm and nginx containers (I use the apache image, but with the php-fpm image it would look similar) and I define the proxy-tier network as external (the name of the network ist FOLDERNAME_NETWORK_NAME):

version: '2'

volumes:
  nextcloud:
  db:

services:
  app:
    image: nextcloud
    links:
      - db
    volumes:
      - nextcloud:/var/www/html
    restart: always
    environment:
      - VIRTUAL_HOST=cloud.example.com
      - LETSENCRYPT_HOST=cloud.example.com
      - [email protected]
    networks:
      - default
      - proxy

  db:
    image: mariadb
    volumes:
      - db:/var/lib/mysql
    environment:
      - MYSQL_ROOT_PASSWORD=
      - MYSQL_DATABASE=
      - MYSQL_USER=
      - MYSQL_PASSWORD=
    restart: always


networks:
  proxy:
    external:
      name: proxy_proxy-tier

And you would do the same for all other services, for example portainer:

version: '2'

networks:
  proxy:
    external:
      name: proxy_proxy-tier

services:
  portainer:
    image: portainer/portainer
    command: -H unix:///var/run/docker.sock
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - portainer_data:/data
    environment:
      - VIRTUAL_HOST=docker.example.com
      - VIRTUAL_PORT=9000
      - LETSENCRYPT_HOST=docker.example.com
      - [email protected]
    networks:
      - proxy

volumes:
  portainer_data:

So I can reach my nextcloud with cloud.example.com and portainer with docker.example.com on the default https port 443.

I hope I could answer a few questions :grin:

All 9 comments

The example you're talking about requires an additional (plain) nginx server, because the php-fpm container, can't serve basic files like images. It's just for php requests and uses port 9000 (this is NOT http!!!). That's why the fpm and nginx container share the nextcloud volume. It also acts as proxy for the php requests and listens on it's http port.

Up to here you have a basic, unencrypted installation (port 80 is exposed by default).

The nginx-proxy is acting as an additional layer on top of the nextcloud-fpm-setup. It listens to the docker daemon and automatically rewrites it's rules to proxy any request to other docker containers, that you have configured accordingly. That's what the environment variables VIRTUAL_HOSTand VIRTUAL_PORT are for. With this setup you get the ability to serve different website on the same port on your host (80 for http and 443 for https) with different domain names and / or subdomains.
Internally the communication between proxy and nextcloud container is unencrypted http (port 80).

With the letsencrypt companion container you also get automatic generation and renewal of letsencrypt certificates, and the proxy will be configured to redirect to https.

If you want to run multiple services on your host, you can just add them to the docker-compose file, or create different compose files, for each service.

For example my setup looks like this:

/docker/
  -> proxy/docke-compose.yml
  -> nextcloud/docker-compose.yml
  -> portainer/docker-compose.yml
  -> wordpress/docker-compose.yml
  -> wordpress2/docker-compose.yml
  -> ldap/docker-compose.yml
  ...

The proxy part just contains the nginx proxy and the letsencrypt companion, as well as a user defined network.

version: '2'

volumes:
  conf.d:
  vhost.d:
  html:
  certs:


services:
  proxy:
    image: jwilder/nginx-proxy:alpine
    ports:
      - 80:80
      - 443:443
    volumes:
      - conf.d:/etc/nginx/conf.d
      - vhost.d:/etc/nginx/vhost.d
      - html:/usr/share/nginx/html
      - certs:/etc/nginx/certs:ro
      - /var/run/docker.sock:/tmp/docker.sock:ro
      - ./uploadsize.conf:/etc/nginx/conf.d/uploadsize.conf:ro
    networks:
      - proxy-tier
    restart: always
    labels:
      - com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy

  letsencrypt:
    image: jrcs/letsencrypt-nginx-proxy-companion
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - certs:/etc/nginx/certs:rw
      - conf.d:/etc/nginx/conf.d:ro
      - html:/usr/share/nginx/html:rw
      - vhost.d:/etc/nginx/vhost.d
    restart: always

networks:
  proxy-tier:

For nextcloud you would just have the database, php-fpm and nginx containers (I use the apache image, but with the php-fpm image it would look similar) and I define the proxy-tier network as external (the name of the network ist FOLDERNAME_NETWORK_NAME):

version: '2'

volumes:
  nextcloud:
  db:

services:
  app:
    image: nextcloud
    links:
      - db
    volumes:
      - nextcloud:/var/www/html
    restart: always
    environment:
      - VIRTUAL_HOST=cloud.example.com
      - LETSENCRYPT_HOST=cloud.example.com
      - [email protected]
    networks:
      - default
      - proxy

  db:
    image: mariadb
    volumes:
      - db:/var/lib/mysql
    environment:
      - MYSQL_ROOT_PASSWORD=
      - MYSQL_DATABASE=
      - MYSQL_USER=
      - MYSQL_PASSWORD=
    restart: always


networks:
  proxy:
    external:
      name: proxy_proxy-tier

And you would do the same for all other services, for example portainer:

version: '2'

networks:
  proxy:
    external:
      name: proxy_proxy-tier

services:
  portainer:
    image: portainer/portainer
    command: -H unix:///var/run/docker.sock
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - portainer_data:/data
    environment:
      - VIRTUAL_HOST=docker.example.com
      - VIRTUAL_PORT=9000
      - LETSENCRYPT_HOST=docker.example.com
      - [email protected]
    networks:
      - proxy

volumes:
  portainer_data:

So I can reach my nextcloud with cloud.example.com and portainer with docker.example.com on the default https port 443.

I hope I could answer a few questions :grin:

thanks a lot for your fast answer!
as nginx can handle the php-requests it should be possible to make a setup with only one instance of nginx.
probably the way the automated jwilder nginx container is creating the server/proxy entries is the reason to have another nginx container in which the location/fastcgi is handled.

so what i mean is, if i wouldnt use the automated nginx container, writing my own server/proxy entries there would be no reason for using 2 nginx containers.

to the topic of the other services running on the jwilder nginx:
i was able to get every container runing as long as i used the standard ports.
i was not able to use a different "outside" port - then i get Error 503.
So for example Pihole ports 8050:80, virtual port 80, virtual_host my.server

For your first question: This should be possible. I just can't help you with that :grin:

I don't know pihole but you have to distinguish between ports (port bindings) and exposed ports. Exposed ports are defined in the dockerfile. Port bindings can only be defined in the docker run command or docker-compose file.

If you use a proxy, you should not add a port binding, because the ports are just used internally in the virtual docker network. You don't want to bind the container ports directly to your host.

For your example I guess you have to add pihole to the proxy network, use VIRTUAL_PORT=8085 and don't use the ports block.

Do you mind posting your compose-file for wordpress as well @SnowMB ? I have a similar compose setup with multiple apps, but I simply can't get the Wordpress-service to run. Using the official image. Error 502 when trying to access it through the configured subdomain. Anyways, informative discussion on this thread

Sure, here you go:


version: '2'

networks:
  proxy-tier:
    external:
      name: proxy_proxy-tier

volumes:
  db:
  files:


services:
  db:
    image: mariadb
    restart: always
    environment:
      - MYSQL_USER=wordpress
      - MYSQL_ROOT_PASSWORD=password
      - MYSQL_PASSWORD=password
      - MYSQL_DATABASE=wordpress
    volumes:
      - db:/var/lib/mysql

  web:
    image: wordpress
    restart: always
    environment:
      - WORDPRESS_DB_HOST=db
      - WORDPRESS_DB_USER=wordpress
      - WORDPRESS_DB_PASSWORD=password
      - VIRTUAL_HOST=wordpress.example.com
      - LETSENCRYPT_HOST=wordpress.example.com
      - [email protected]
    depends_on:
      - db
    volumes:
      - files:/var/www/html
    networks:
      - default
      - proxy-tier

Thanks @SnowMB! I had not included the networks entry for the wordpress container. Now it's up! But to be honest I don't understand the difference between networks: and VIRTUAL_NETWORK under environment:. Nor the difference between ports: and VIRTUAL_PORTS. Anyways, thanks for your help!

networks: and ports: are settings for docker to correctly configure and connect your containers. The environment variables (VIRTUAL_PORT and VIRTUAL_NETWORK) are completely irrelevant for docker. They are however important for the proxy container. The nginx proxy gets access to the docker socket running on your host. It now monitors all changes (containers getting started and stopped). When a new container is started it checks its Environment variables to automatically generate the routing configuration for that container.

I had a follow up question to this thread. As I understand, the nextcloud-fpm container will handle the php execution. We spin up another container (vanilla nginx) to handle calls to static content - it is referred to as web. However, this web container as the variable VIRTUAL_HOST attached to it. I would think that variable should go on the nextcloud-fpm container? But that doesn't work.

So if i wanted to add additional services that required a webserver, would i need to spin up an additional nginx server because the 'web' container will only serve up nextcloud content because of the VIRTUAL_HOST parameter on it? Thanks in advance for any help!

The nginx instance does not only serves static files, but is also the http - frontend for the php-fpm process. A user connects via http(s) through the reverse proxy to the nginx webserver. The webserver then talks to it's php-fpm backend. So the reverse proxy only has to know and reach the nginx container.

The nginx container only serves nextcloud because of the conf.d file. Theoretically you can also give it additional services there and could use multiple domains for VIRTUAL_HOST and LETSENCRYPT_HOST but this goes against the docker philosophy.

In a containerized world you really want to spin up a web server for each service to minimize coupling between the services. When your nextcloud goes down it shouldn't affect what else runs on the server.

Was this page helpful?
0 / 5 - 0 ratings