Docker-compose.yml services for php-fpm

Hi all,
I’m a docker newbie, this is my environment : CentOS Linux release 7.4.1708 (Core) + Docker version 18.03.1-ce, build 9ee9f40 .
This is my Dockerfile for php-fpm image container

From centos:7

#Install centos-release-scl-rh
RUN yum install -y --setopt=tsflags=nodocs centos-release-scl-rh &&
yum install -y --setopt=tsflags=nodocs
rh-php70-php.x86_64
rh-php70-php-bcmath.x86_64
rh-php70-php-common.x86_64
rh-php70-php-devel.x86_64
rh-php70-php-enchant.x86_64
rh-php70-php-fpm.x86_64
rh-php70-php-gd.x86_64
rh-php70-php-intl.x86_64
rh-php70-php-json.x86_64
rh-php70-php-ldap.x86_64
rh-php70-php-mbstring.x86_64
rh-php70-php-mysqlnd.x86_64
rh-php70-php-pear.noarch
rh-php70-php-pspell.x86_64
rh-php70-php-process.x86_64
rh-php70-php-recode.x86_64
rh-php70-php-snmp.x86_64
rh-php70-php-soap.x86_64
rh-php70-php-xml.x86_64
rh-php70-php-zip.x86_64 &&
yum clean all

EXPOSE 9003
#start php-fpm70
CMD [ “/opt/rh/rh-php70/root/usr/sbin/php-fpm”, “–nodaemonize”]

I build it successfully and can run container
docker build -t php-fpm70 .
docker run -d -p 9003:9003 php-fpm70
There will be php process running and listen at port 9003
Now I want to create service for php-fpm70 container, this my docker-compose.yml

version: “3”
services:
php-fpm70:
image: php-fpm70
volumes:
- ./php-fpm.conf:/etc/opt/rh/rh-php70/php-fpm.conf
- ./php.ini:/etc/opt/rh/rh-php70/php.ini
- ./www.conf:/etc/opt/rh/rh-php70/php-fpm.d/www.conf
ports:
- “9003:9003”
networks:
- webnet
networks:
webnet:

I place php-fpm.conf , php.ini , www.conf at same path with docker-compose.yml and I want to map these file from host to container, leave other php config files for container, then I start service

docker stack deploy -c docker-compose.yml php-fpm70
Creating network php-fpm70_webnet
Creating service php-fpm70_php-fpm70

docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
i3s74xfa64td php-fpm70_php-fpm70 replicated 0/1 php-fpm70:latest *:9003->9003/tcp

docker stack ls
NAME SERVICES
php-fpm70 1

docker service ps php-fpm70_php-fpm70
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
pg97ujtl5904 php-fpm70_php-fpm70.1 php-fpm70:latest localhost.localdomain Ready Preparing 4 seconds ago
qsdw2fj4atu4 _ php-fpm70_php-fpm70.1 php-fpm70:latest localhost.localdomain Shutdown Failed 4 seconds ago “task: non-zero exit (78)”
yotqmrqo72fx _ php-fpm70_php-fpm70.1 php-fpm70:latest localhost.localdomain Shutdown Failed 11 seconds ago “task: non-zero exit (78)”
quwu1lhn8agh _ php-fpm70_php-fpm70.1 php-fpm70:latest localhost.localdomain Shutdown Failed 18 seconds ago “task: non-zero exit (78)”
w1hsgq3ztksw _ php-fpm70_php-fpm70.1 php-fpm70:latest localhost.localdomain Shutdown Failed 25 seconds ago “task: non-zero exit (78)”

There is no php process running and listen at port 9003. I did something wrong ?
Please give me some advice, thank you very much.

When you are using docker stack deploy it will not mount your config files relative to your docker-compose.yml file because stack deploy deploys the yaml as a Swarm stack and the Swarm services could end up running on any host in the Swarm cluster.

Were you intending to deploy it as a Swarm stack or did you mean to do a docker-compose up and deploy it only on the local Docker host?

Hi Kapono,
I am intending to deploy it as a Swarm stack but this Swarm cluster has only 1 member now, so it is same as doing a docker-compose up and deploy it only on the local Docker host ?
If I remove “volumes” part from docker-compose.yml

I can use docker stack deploy or docker-compose up to run it. So how can I mount my config files from host to container ?

The best way to do it would probably be to use Swarm configs. Then, when you do a docker stack deploy, it will deploy your configuration files to the cluster where every host will be able to get to them.

version: “3”
  services:
    php-fpm70:
      cimage: php-fpm70
      configs:
        - source: php-fpm.conf.1
          target: /etc/opt/rh/rh-php70/php-fpm.conf
        - source: php.ini.1
          target: /etc/opt/rh/rh-php70/php.ini
        - source: www.conf.1
          target: /etc/opt/rh/rh-php70/php-fpm.d/www.conf
      ports:
        - “9003:9003”
      networks:
        - webnet

configs:
  php-fpm.conf.1:
    file: ./php-fpm.conf
  php.ini.1:
    file: ./php.ini
  www.conf.1:
    file: ./www.conf

networks:
  webnet:

The largest annoyance to using Swarm configs is the fact that you must manually update the version of the config every time that you change the config file ( that is why I put a .1 after all of the config names ). If you want to change www.conf, for example, you would:

  1. Update www.conf with the changes you want to make.
  2. Increment the numbered suffix of the config name in your docker-compose.yml ( i.e. changing both occurrences of www.conf.1 to www.conf.2.
  3. Re-run docker stack deploy [stack name] to deploy the services with the updated config.

Other than that it should work exactly how you would want it to. The config will be available to the service no matter what host it runs on.

1 Like

Note: The .number after each config name was just a convention that I made. It doesn’t really matter how you version the configs as long as it has a different name.

Note: The old configs will not be removed when you update the stack by incrementing the config’s version number. You can see a list of all configs with docker config ls and you can remove any old configs you do not want with docker config rm.

Hi kapono,
For ex, assume that:

  • I build nginx and php-fpm70 image as default , so nginx container will read config files from /etc/nginx/nginx.conf , /etc/nginx/fastcgi_params , /etc/nginx/fastcgi.conf and /etc/nginx/conf.d/*.conf . php-fpm70 container will read config files from /etc/opt/rh/rh-php70/php-fpm.conf , /etc/opt/rh/rh-php70/php.ini , /etc/opt/rh/rh-php70/php-fpm.d/www.conf .
  • I prepare and place my nginx config on host at /opt/nginx/nginx.conf and /opt/nginx/conf.d/ (many virtual host config here)
  • I prepare and place my php-fpm70 config on host at /opt/php-fpm70/php-fpm.conf , /opt/php-fpm-70/php.ini , /opt/php-fpm-70/www.conf
  • I place my websites code on host at /opt/www
    And I want to map above files, folders from host to my containers.
    This is my docker-compose.yml
version: "3"
services:
  php-fpm70:
    image: php-fpm70
    deploy:
      replicas: 2
      restart_policy:
        condition: on-failure
    configs:
      - source: php-fpm.conf.1
        target: /etc/opt/rh/rh-php70/php-fpm.conf
      - source: php.ini.1
        target: /etc/opt/rh/rh-php70/php.ini
      - source: www.conf.1
        target: /etc/opt/rh/rh-php70/php-fpm.d/www.conf
    ports:
      - "9003:9003"
    networks:
      - webnet
  web:
    image: nginx
    deploy:
      replicas: 2
      restart_policy:
        condition: on-failure
    configs:
      - source: nginx.conf
        target: /etc/nginx.conf
      - source: nginx-conf.d
        target: /etc/nginx/conf.d/test.conf
      - source: code
        target: /opt/www
    ports:
      - "80:80"
      - "443:443"
    networks:
      - webnet

configs:
  nginx.conf:
    file: ./opt/nginx/nginx.conf
  nginx-conf.d:
    file: ./opt/nginx/conf.d/test.conf
### websites source code folder
  code:
    file: /opt/www
  php-fpm.conf.1:
    file: ./opt/php-fpm70/php-fpm.conf
  php.ini.1:
    file: ./opt/php-fpm70/php.ini
  www.conf.1:
    file: ./opt/php-fpm70/www.conf

networks:
   webnet:

How can I map a folder to configs ?
And I get error “configs Additional property configs is not allowed” with docker stack deploy -c docker-compose.yml myservices when I add configs: into docker-compose.yml

You can’t. You would have to make each individual config file a separate config.

For source code, you don’t want to put that in a config, nor is is possible because configs cannot be a directory and they have a maximum size that is only so many kilobytes ( I can’t remember exactly how many ).

Source code should be built into the Docker image. For your website you would have a Dockerfile that looks like this:

FROM nginx

# Copy the source code from a directory relative to the Dockerfile
# to the `/opt/www` directory in the container
COPY ./www /opt/www

From the same directory as the Dockerfile you would build your Dockerfile like this:

$ docker build -t mydockerusername/mywebsitename .

Then in your docker-compose.yml you would set the image of the web service to mydockerusername/mywebsitename.

...
  web:
    image: mydockerusername/mywebsitename
...
1 Like

The version at the top of your docker-compose.yml has to be at least 3.3 to support configs.

1 Like

Thank you very much kapono,
So with “configs” I would have to make each individual config file a separate config , then I guess I have to put all my nginx virtual host config websites into 1 file /opt/nginx/conf.d/sites.conf and map it into /etc/nginx/conf.d/sites.conf in container.
I want to keep my nginx Docker image file as simple as possible and leave the “configuration” part for docker-compose.yml so I don’t build source code into image, I would use “volumes” in docker-compose.yml. I can also use “volumes” to map nginx and php-fpm config

version: "3.3"
services:
  php-fpm70:
    image: php-fpm70
    deploy:
      replicas: 2
      restart_policy:
        condition: on-failure
    configs:
# override php-fpm config incontainer
# My www.conf on host has "listen = 127.0.0.1:9003" , I want to change php-fpm port from 9000 #(default) to 9003
      - source: www.conf.1
        target: /etc/opt/rh/rh-php70/php-fpm.d/www.conf
    ports:
      - "9003:9003"
    networks:
      - webnet
  web:
    image: nginx
    deploy:
      replicas: 2
      restart_policy:
        condition: on-failure
    configs:
      - source: nginx.conf
        target: /etc/nginx.conf
      - source: nginx-conf.d
        target: /etc/nginx/conf.d/test.conf
    volumes:
      - /opt/www:/opt/www
    ports:
      - "80:80"
      - "443:443"
    networks:
      - webnet

configs:
  nginx.conf:
    file: /opt/nginx/nginx.conf
  nginx-conf.d:
    file: /opt/nginx/conf.d/test.conf
  www.conf.1:
    file: /opt/php-fpm70/www.conf

networks:
   webnet:

test.conf

server {
        listen 80;
        server_name test.mydomain.com;
    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;
    root /opt/www/test/;
    gzip  on;
gzip_comp_level 9;
gzip_min_length 1000;
gzip_proxied off;
gzip_types text/plain text/css application/xml+html application/javascript image/jpeg image/x-icon image/gif image/png video/jpeg;
gzip_disable "MSIE [1-6]\.";

index index.html index.htm index.php;

location / {
               try_files $uri $uri/ /index.php?/$request_uri;
        }

    location ~ \.php$ {
        fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
        include         fastcgi_params;
    fastcgi_split_path_info ^(.+\.php)(/.+)$;
    fastcgi_read_timeout 150;
        fastcgi_pass  127.0.0.1:9003;

    fastcgi_index index.php;

    }
}
docker stack deploy -c docker-compose.yml getstartedlab
Creating config getstartedlab_nginx.conf
Creating config getstartedlab_nginx-conf.d
Creating config getstartedlab_www.conf.1
Creating service getstartedlab_php-fpm70

docker service ls
ID                  NAME                      MODE                REPLICAS            IMAGE               PORTS
ro90cgmejbzg        getstartedlab_php-fpm70   replicated          2/2                 php-fpm70:latest    *:9003->9003/tcp
6h34rx3lpclw        getstartedlab_web         replicated          2/2                 nginx:latest        *:80->80/tcp, *:443->443/tcp

I can access to test.mydomain.com --> “Welcome to nginx” , but I cannot access test.mydomain.com/abc.php which actually show phpinfo --> “502 Bad Gateway”, here log result

docker service logs getstartedlab_web
getstartedlab_web.2.euqwfo51t392@localhost.localdomain    | 10.255.0.2 - - [25/Jun/2018:02:32:44 +0000] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:60.0) Gecko/20100101 Firefox/60.0"
getstartedlab_web.2.euqwfo51t392@localhost.localdomain    | 2018/06/25 02:32:44 [error] 7#7: *1 connect() failed (111: Connection refused) while connecting to upstream, client: 10.255.0.2, server: test.mydomain.com, request: "GET /nginx-logo.png HTTP/1.1", upstream: "fastcgi://127.0.0.1:9003", host: "test.mydomain.com", referrer: "http://test.mydomain.com/"
getstartedlab_web.2.euqwfo51t392@localhost.localdomain    | 10.255.0.2 - - [25/Jun/2018:02:32:44 +0000] "GET /nginx-logo.png HTTP/1.1" 502 173 "http://test.mydomain.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:60.0) Gecko/20100101 Firefox/60.0"
getstartedlab_web.2.euqwfo51t392@localhost.localdomain    | 2018/06/25 02:32:44 [error] 7#7: *1 connect() failed (111: Connection refused) while connecting to upstream, client: 10.255.0.2, server: test.itlvn.com, request: "GET /poweredby.png HTTP/1.1", upstream: "fastcgi://127.0.0.1:9003", host: "test.mydomain.com", referrer: "http://test.mydomain.com/"
getstartedlab_web.2.euqwfo51t392@localhost.localdomain    | 10.255.0.2 - - [25/Jun/2018:02:32:44 +0000] "GET /poweredby.png HTTP/1.1" 502 173 "http://test.mydomain.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:60.0) Gecko/20100101 Firefox/60.0"
getstartedlab_web.2.euqwfo51t392@localhost.localdomain    | 2018/06/25 02:32:44 [error] 7#7: *1 connect() failed (111: Connection refused) while connecting to upstream, client: 10.255.0.2, server: test.mydomaincom, request: "GET /favicon.ico HTTP/1.1", upstream: "fastcgi://127.0.0.1:9003", host: "test.mydomain.com"
getstartedlab_web.2.euqwfo51t392@localhost.localdomain    | 10.255.0.2 - - [25/Jun/2018:02:32:44 +0000] "GET /favicon.ico HTTP/1.1" 502 173 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:60.0) Gecko/20100101 Firefox/60.0"
getstartedlab_web.2.euqwfo51t392@localhost.localdomain    | 2018/06/25 02:32:49 [error] 7#7: *1 connect() failed (111: Connection refused) while connecting to upstream, client: 10.255.0.2, server: test.mydomain.com, request: "GET /abc.php HTTP/1.1", upstream: "fastcgi://127.0.0.1:9003", host: "test.mydomain.com"
getstartedlab_web.2.euqwfo51t392@localhost.localdomain    | 10.255.0.2 - - [25/Jun/2018:02:32:49 +0000] "GET /abc.php HTTP/1.1" 502 173 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:60.0) Gecko/20100101 Firefox/60.0"

Is there something wrong with docker network so nginx container cannot connect with php-fpm70 container ?

docker network ls
NETWORK ID          NAME                   DRIVER              SCOPE
11c892f5a3ce        bridge                 bridge              local
78fb32ab0e58        docker_gwbridge        bridge              local
mqqwymlin3rf        getstartedlab_webnet   overlay             swarm
270e6709c894        host                   host                local
ojmz46usbxhh        ingress                overlay             swarm
ffbf775e969e        none                   null                local

docker network inspect getstartedlab_webnet
[
    {
        "Name": "getstartedlab_webnet",
        "Id": "mqqwymlin3rftfygxr2bwx4qs",
        "Created": "2018-06-25T09:32:24.283490233+07:00",
        "Scope": "swarm",
        "Driver": "overlay",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "10.0.0.0/24",
                    "Gateway": "10.0.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "59c8fb05edd8c8ba314cc2c1f9caa81ec7181c81f4e4b917c0dbfed8e9188dd6": {
                "Name": "getstartedlab_php-fpm70.2.5o4gch2n0konf5culk98hvrch",
                "EndpointID": "263dc13ceb778e7a55ecf86618c0ee31efcba226c9e522e1cb530fff630dfd80",
                "MacAddress": "02:42:0a:00:00:05",
                "IPv4Address": "10.0.0.5/24",
                "IPv6Address": ""
            },
            "5dde77f29ca0367bf3a589d50c41cc6c56b4e5abbb92922d7076ba28f0e18609": {
                "Name": "getstartedlab_php-fpm70.1.qverc3gkt800hg944eoqomhwx",
                "EndpointID": "d13a5a30425c135ed4a9deec557d0a14a9b48b9bb3bcbf2fcd67147e4bdea1c4",
                "MacAddress": "02:42:0a:00:00:04",
                "IPv4Address": "10.0.0.4/24",
                "IPv6Address": ""
            },
            "c620edfe30885d16d0b0162a6591931c7b950e4b5d750eb8613555be68246ddc": {
                "Name": "getstartedlab_web.1.lo33mv0xcy37j7b8bbs2lzoms",
                "EndpointID": "4f2c64ceeff31c7bf37a5ed921a307af8606eed0454e04357399bf110363aa8e",
                "MacAddress": "02:42:0a:00:00:07",
                "IPv4Address": "10.0.0.7/24",
                "IPv6Address": ""
            },
            "eadf040c574f8d4eecafbe53210b14a6e699bdf21236bb876f2518f5cc4fdfb5": {
                "Name": "getstartedlab_web.2.euqwfo51t3925tswuicjx4w1y",
                "EndpointID": "5381233c29028a51c182cc08fe1a7e667b726b5a6f3d0016e5cc5c6ea7e99ee3",
                "MacAddress": "02:42:0a:00:00:08",
                "IPv4Address": "10.0.0.8/24",
                "IPv6Address": ""
            }
        },
        "Options": {
            "com.docker.network.driver.overlay.vxlanid_list": "4097"
        },
        "Labels": {
            "com.docker.stack.namespace": "getstartedlab"
        },
        "Peers": [
            {
                "Name": "80132ab1facf",
                "IP": "192.168.0.124"
            }
        ]
    }
]

On host, I can see there is nginx and php-fpm containers are runnung, I cant telnet telnet 127.0.0.1 80 but cannot telnet 127.0.0.1 9003.

The problem is php-fpm70 container cannot be connected from outsite, and I figure out reason.
I have to comment out line “listen.allowed_clients = 127.0.0.1” in my php-fpm www.conf to allow connection from outside the container.
I want to run many php-fpm version instances (each container) at same time so I have to change php-fpm config to change listen port. This is final OK configuration

version: "3.3"
services:
  php-fpm70:
    deploy:
      replicas: 2
      restart_policy:
        condition: on-failure
    configs:
      - source: www.conf.1
        target: /etc/opt/rh/rh-php70/php-fpm.d/www.conf
    volumes:
      - /opt/www:/opt/www
    ports:
      - "9003:9003"
    networks:
      - webnet
  web:
    image: nginx
    deploy:
      replicas: 2
      restart_policy:
        condition: on-failure
    configs:
      - source: nginx.conf
        target: /etc/nginx.conf
      - source: nginx-conf.d
        target: /etc/nginx/conf.d/test.conf
    volumes:
      - /opt/www:/opt/www
    ports:
      - "80:80"
      - "443:443"
    networks:
      - webnet

configs:
  nginx.conf:
    file: /opt/nginx/nginx.conf
  nginx-conf.d:
    file: /opt/nginx/conf.d/test.conf
  www.conf.1:
    file: /opt/php-fpm70/www.conf

networks:
   webnet:

test.conf

server {
        listen 80;
        server_name test.mydomain.com;
    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;
    root /opt/www/test/;
    gzip  on;
gzip_comp_level 9;
gzip_min_length 1000;
gzip_proxied off;
gzip_types text/plain text/css application/xml+html application/javascript image/jpeg image/x-icon image/gif image/png video/jpeg;
gzip_disable "MSIE [1-6]\.";

index index.html index.htm index.php;

    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
# change here a little bit
        fastcgi_pass php-fpm70:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }
}

www.conf

...
listen = 9003
;listen.allowed_clients = 127.0.0.1
...

Thank you very much.

Awesome! I’m glad you got it working. :smiley:

I just have one note: because you put the source code in a volume, the source code will only be accessible from the hosts that you have the source code on. If you have two nodes in your Swarm you would have to make sure that the source code gets created at /opt/www on both of the hosts. The only way to avoid this is to use a network filesystem such as NFS or a distributed filesystem such as LizardFS. I created a Docker plugin for using LizardFS on Swarm, but that is likely overkill for you use-case.