140

I am trying to link 2 separate containers:

The problem is that php scripts do not work. Perhaps the php-fpm configuration is incorrect. Here is the source code, which is in my repository. Here is the file docker-compose.yml:

nginx:
    build: .
    ports:
        - "80:80"
        - "443:443"
    volumes:
        - ./:/var/www/test/
    links:
        - fpm
fpm:
    image: php:fpm
    ports:
        - "9000:9000"

and Dockerfile which I used to build a custom image based on the nginx one:

FROM nginx

# Change Nginx config here...
RUN rm /etc/nginx/conf.d/default.conf
ADD ./default.conf /etc/nginx/conf.d/

Lastly, here is my custom Nginx virtual host config:

server {
    listen  80;

    server_name localhost;
    root /var/www/test;

    error_log /var/log/nginx/localhost.error.log;
    access_log /var/log/nginx/localhost.access.log;

    location / {
        # try to serve file directly, fallback to app.php
        try_files $uri /index.php$is_args$args;
    }

    location ~ ^/.+\.php(/|$) {
        fastcgi_pass 192.168.59.103:9000;
        fastcgi_split_path_info ^(.+\.php)(/.*)$;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param HTTPS off;
    }
}

Could anybody help me configure these containers correctly to execute php scripts?

P.S. I run containers via docker-composer like this:

docker-compose up

from the project root directory.

zkilnbqi
  • 1,141
  • 11
  • 23
Victor Bocharsky
  • 11,930
  • 13
  • 58
  • 91

8 Answers8

131

I know it is kind an old post, but I've had the same problem and couldn't understand why your code didn't work. After a LOT of tests I've found out why.

It seems like fpm receives the full path from nginx and tries to find the files in the fpm container, so it must be the exactly the same as server.root in the nginx config, even if it doesn't exist in the nginx container.

To demonstrate:

docker-compose.yml

nginx:
    build: .
    ports:
        - "80:80"
    links:
        - fpm
fpm:
    image: php:fpm
    ports:
        - ":9000"

    # seems like fpm receives the full path from nginx
    # and tries to find the files in this dock, so it must
    # be the same as nginx.root
    volumes:
        - ./:/complex/path/to/files/

/etc/nginx/conf.d/default.conf

server {
    listen  80;

    # this path MUST be exactly as docker-compose.fpm.volumes,
    # even if it doesn't exist in this dock.
    root /complex/path/to/files;

    location / {
        try_files $uri /index.php$is_args$args;
    }

    location ~ ^/.+\.php(/|$) {
        fastcgi_pass fpm:9000;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }
}

Dockerfile

FROM nginx:latest
COPY ./default.conf /etc/nginx/conf.d/
Potherca
  • 13,207
  • 5
  • 76
  • 94
Rafael Quintela
  • 1,908
  • 2
  • 12
  • 14
  • 11
    **WELL DONE!!!** That's exact the point! I set the nginx root to an alternative path other than `/var/www/html` with failure. – Alfred Huang Jan 23 '16 at 14:11
  • 3
    Also, just a note that `:9000` is the port that's being used in the container not the one that's exposed to your host. Took me 2 hours to figure this out. Hopefully, you don't have to. – shriek Jun 04 '16 at 12:02
  • 1
    `services.fpm.ports is invalid: Invalid port ":9000", should be [[remote_ip:]remote_port[-remote_port]:]port[/protocol]` – 030 Apr 04 '17 at 06:59
  • 8
    You don't actually need to include a `ports` section at all here. You may just need to `expose` it if it's not already in the image (which it probably is). If you're doing inter-container communication, you _shouldn't_ be exposing the PHP-FPM port. – Seer Jun 02 '17 at 13:47
  • 2
    Was looking for a solution for `AH01071: Got error 'Primary script unknown\n'` and that the php-fpm container has to share the same directory with the web nodes was the solution! – cptPH May 06 '18 at 20:24
  • @cptPH nothing to be shared actually, I do not know wich image is reporting that error, but fpm image must know about `/complex/path/to/files`, and that must be exactly the path where the php files lay on, including index.php, that is a fallback as defined in `default.conf` BUT, if you are providing anything else than php, then that path must exists in nginx too. (common, but I am using this for a pure rest service, so I do not need it) – Daniele Cruciani Jan 29 '20 at 09:12
  • There is one thing that should be unnecessary: Creating an own image for Nginx. Instead, create a single configuration file that you mount as configuration file into the container. Instead of the `include` for the single site, put that inline. Multiple sites from one webserver are not necessary in a container environment. – Ulrich Eckhardt Apr 29 '20 at 16:26
  • another option could be using `fastcgi_param SCRIPT_FILENAME /var/www/html/$fastcgi_script_name;`, provided that `/var/www/html/` is path inside php-fpm container. – simon Apr 20 '22 at 16:53
  • Doing the ./:.... part gave me "Volume name is too short, names should be at least two alphanumeric characters" – Raul Chiarella Sep 12 '22 at 06:10
  • I additionally had to do the same volume definition also for `nginx`, otherwise I had an error that `/var/www/html/index.php` was not found. Any ideas? – fabpico Dec 27 '22 at 09:19
39

Don't hardcode ip of containers in nginx config, docker link adds the hostname of the linked machine to the hosts file of the container and you should be able to ping by hostname.

EDIT: Docker 1.9 Networking no longer requires you to link containers, when multiple containers are connected to the same network, their hosts file will be updated so they can reach each other by hostname.

Every time a docker container spins up from an image (even stop/start-ing an existing container) the containers get new ip's assigned by the docker host. These ip's are not in the same subnet as your actual machines.

see docker linking docs (this is what compose uses in the background)

but more clearly explained in the docker-compose docs on links & expose

links

links:
 - db
 - db:database
 - redis

An entry with the alias' name will be created in /etc/hosts inside containers for this service, e.g:

172.17.2.186  db
172.17.2.186  database
172.17.2.187  redis

expose

Expose ports without publishing them to the host machine - they'll only be accessible to linked services. Only the internal port can be specified.

and if you set up your project to get the ports + other credentials through environment variables, links automatically set a bunch of system variables:

To see what environment variables are available to a service, run docker-compose run SERVICE env.

name_PORT

Full URL, e.g. DB_PORT=tcp://172.17.0.5:5432

name_PORT_num_protocol

Full URL, e.g. DB_PORT_5432_TCP=tcp://172.17.0.5:5432

name_PORT_num_protocol_ADDR

Container's IP address, e.g. DB_PORT_5432_TCP_ADDR=172.17.0.5

name_PORT_num_protocol_PORT

Exposed port number, e.g. DB_PORT_5432_TCP_PORT=5432

name_PORT_num_protocol_PROTO

Protocol (tcp or udp), e.g. DB_PORT_5432_TCP_PROTO=tcp

name_NAME

Fully qualified container name, e.g. DB_1_NAME=/myapp_web_1/myapp_db_1

Jason Gilmore
  • 3,698
  • 3
  • 23
  • 28
Vincent De Smet
  • 4,859
  • 2
  • 34
  • 41
  • 2
    you also don't need to publish port 9000 on the host, ports are open between the linked docker containers, unless you want to troubleshoot the port directly from your host. – Vincent De Smet Apr 29 '15 at 07:08
  • Yeas, you're right, thanks. In my case I should use **fastcgi_pass fpm:9000** instead of direct ip. I don't know that Docker add it to host automatically, my bad. – Victor Bocharsky Apr 30 '15 at 06:11
  • What about port, so better to use **expose** instead of **ports**? Or I could not use any of this ports and expose directives because linked containers will have access to this port? – Victor Bocharsky Apr 30 '15 at 06:19
  • sorry for late reply - I think you may need to use expose, sorry I can't check right now – Vincent De Smet Apr 30 '15 at 10:48
  • here is full reference of docker-compose links - http://docs.docker.com/compose/yml/#links I add this info to my answer – Vincent De Smet Apr 30 '15 at 11:02
  • 3
    `--links` are now obsolete, according to the docker documention that you reference. They are still _currently_ supported but the apparent plan is for them to be obsoleted. – therobyouknow Jun 13 '18 at 19:00
  • also as per EDIT in answer. And the docker 'expose' statement can be used to connect to a specific port on another container (when used with the hostname) as the question answerer advises. – therobyouknow Jun 13 '18 at 19:03
  • Hey. I linked them on the same network, and also pointed the same mount directories but the problem persists. t.t – Raul Chiarella Sep 12 '22 at 06:38
28

As pointed out before, the problem was that the files were not visible by the fpm container. However to share data among containers the recommended pattern is using data-only containers (as explained in this article).

Long story short: create a container that just holds your data, share it with a volume, and link this volume in your apps with volumes_from.

Using compose (1.6.2 in my machine), the docker-compose.yml file would read:

version: "2"
services:
  nginx:
    build:
      context: .
      dockerfile: nginx/Dockerfile
    ports:
      - "80:80"
    links:
      - fpm
    volumes_from:
      - data
  fpm:
    image: php:fpm
    volumes_from:
      - data
  data:
    build:
      context: .
      dockerfile: data/Dockerfile
    volumes:
      - /var/www/html

Note that data publishes a volume that is linked to the nginx and fpm services. Then the Dockerfile for the data service, that contains your source code:

FROM busybox

# content
ADD path/to/source /var/www/html

And the Dockerfile for nginx, that just replaces the default config:

FROM nginx

# config
ADD config/default.conf /etc/nginx/conf.d

For the sake of completion, here's the config file required for the example to work:

server {
    listen 0.0.0.0:80;

    root /var/www/html;

    location / {
        index index.php index.html;
    }

    location ~ \.php$ {
        include fastcgi_params;
        fastcgi_pass fpm:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root/$fastcgi_script_name;
    }
}

which just tells nginx to use the shared volume as document root, and sets the right config for nginx to be able to communicate with the fpm container (i.e.: the right HOST:PORT, which is fpm:9000 thanks to the hostnames defined by compose, and the SCRIPT_FILENAME).

iKanor
  • 513
  • 5
  • 7
  • Looks like the Data does not get update from host to the containers, and when I do docker ps -a I see the data container stopped, is that an issue? – Aftab Naveed Sep 16 '16 at 06:59
  • 2
    That's the expected behaviour. A data-only container does not run any command, and it will just be listed as stopped. Also, the `Dockerfile` of the data container is copying your sources to the container on build time. That is why they will not be updated if you change the files in the host. If you want to share the sources between the host and the container you need to mount the directory. Change the `data` service in the compose file to load `image: busybox`, and in the `volumes` section enter `./sources:/var/www/html`, where `./sources` is the path to your sources in the host. – iKanor Sep 24 '16 at 11:45
  • Link to blog article is not working anymore: https://web.archive.org/web/20200621204615/http://crosbymichael.com/advanced-docker-volumes.html – Stefan Jan 03 '23 at 20:02
18

New Answer

Docker Compose has been updated. They now have a version 2 file format.

Version 2 files are supported by Compose 1.6.0+ and require a Docker Engine of version 1.10.0+.

They now support the networking feature of Docker which when run sets up a default network called myapp_default

From their documentation your file would look something like the below:

version: '2'

services:
  web:
    build: .
    ports:
      - "8000:8000"
  fpm:
    image: phpfpm
  nginx
    image: nginx

As these containers are automatically added to the default myapp_default network they would be able to talk to each other. You would then have in the Nginx config:

fastcgi_pass fpm:9000;

Also as mentioned by @treeface in the comments remember to ensure PHP-FPM is listening on port 9000, this can be done by editing /etc/php5/fpm/pool.d/www.conf where you will need listen = 9000.

Old Answer

I have kept the below here for those using older version of Docker/Docker compose and would like the information.

I kept stumbling upon this question on google when trying to find an answer to this question but it was not quite what I was looking for due to the Q/A emphasis on docker-compose (which at the time of writing only has experimental support for docker networking features). So here is my take on what I have learnt.

Docker has recently deprecated its link feature in favour of its networks feature

Therefore using the Docker Networks feature you can link containers by following these steps. For full explanations on options read up on the docs linked previously.

First create your network

docker network create --driver bridge mynetwork

Next run your PHP-FPM container ensuring you open up port 9000 and assign to your new network (mynetwork).

docker run -d -p 9000 --net mynetwork --name php-fpm php:fpm

The important bit here is the --name php-fpm at the end of the command which is the name, we will need this later.

Next run your Nginx container again assign to the network you created.

docker run --net mynetwork --name nginx -d -p 80:80 nginx:latest

For the PHP and Nginx containers you can also add in --volumes-from commands etc as required.

Now comes the Nginx configuration. Which should look something similar to this:

server {
    listen 80;
    server_name localhost;

    root /path/to/my/webroot;

    index index.html index.htm index.php;

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

    location ~ \.php$ {
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass php-fpm:9000; 
        fastcgi_index index.php;
        include fastcgi_params;
    }
}

Notice the fastcgi_pass php-fpm:9000; in the location block. Thats saying contact container php-fpm on port 9000. When you add containers to a Docker bridge network they all automatically get a hosts file update which puts in their container name against their IP address. So when Nginx sees that it will know to contact the PHP-FPM container you named php-fpm earlier and assigned to your mynetwork Docker network.

You can add that Nginx config either during the build process of your Docker container or afterwards its up to you.

DavidT
  • 2,341
  • 22
  • 30
  • Also remember to make sure `php-fpm` is listening on port 9000. This would be `listen = 9000` in `/etc/php5/fpm/pool.d/www.conf`. – treeface Jul 24 '16 at 05:48
  • Thanks @treeface good point. I have updated with your comment. – DavidT Jul 25 '16 at 09:03
11

As previous answers have solved for, but should be stated very explicitly: the php code needs to live in the php-fpm container, while the static files need to live in the nginx container. For simplicity, most people have just attached all the code to both, as I have also done below. If the future, I will likely separate out these different parts of the code in my own projects as to minimize which containers have access to which parts.

Updated my example files below with this latest revelation (thank you @alkaline )

This seems to be the minimum setup for docker 2.0 forward (because things got a lot easier in docker 2.0)

docker-compose.yml:

version: '2'
services:
  php:
    container_name: test-php
    image: php:fpm
    volumes:
      - ./code:/var/www/html/site
  nginx:
    container_name: test-nginx
    image: nginx:latest
    volumes:
      - ./code:/var/www/html/site
      - ./site.conf:/etc/nginx/conf.d/site.conf:ro
    ports:
      - 80:80

(UPDATED the docker-compose.yml above: For sites that have css, javascript, static files, etc, you will need those files accessible to the nginx container. While still having all the php code accessible to the fpm container. Again, because my base code is a messy mix of css, js, and php, this example just attaches all the code to both containers)

In the same folder:

site.conf:

server
{
    listen   80;
    server_name site.local.[YOUR URL].com;

    root /var/www/html/site;
    index index.php;

    location /
    {
        try_files $uri =404;
    }

    location ~ \.php$ {
        fastcgi_pass   test-php:9000;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
        include        fastcgi_params;
    }
}

In folder code:

./code/index.php:

<?php
phpinfo();

and don't forget to update your hosts file:

127.0.0.1 site.local.[YOUR URL].com

and run your docker-compose up

$docker-compose up -d

and try the URL from your favorite browser

site.local.[YOUR URL].com/index.php
Phillip
  • 171
  • 1
  • 2
  • 9
  • 1
    Your nginx config file assumes that your website has only php files. It's a best practice to create an nginx location rule for static files (jpg,txt,svg, ...) and avoid the php interpreter. In that case both the nginx and php containers need access to the website files. @iKanor 's answer above takes care of that. – Bernard Jul 09 '16 at 13:24
  • Thanks @Alkaline , static files are problem with my original answer. In fact, nginx really needs, at a minimum, the css and js files to be local to that machine in order to work properly. – Phillip Jul 14 '16 at 18:06
9

I think we also need to give the fpm container the volume, dont we? So =>

fpm:
    image: php:fpm
    volumes:
        - ./:/var/www/test/

If i dont do this, i run into this exception when firing a request, as fpm cannot find requested file:

[error] 6#6: *4 FastCGI sent in stderr: "Primary script unknown" while reading response header from upstream, client: 172.17.42.1, server: localhost, request: "GET / HTTP/1.1", upstream: "fastcgi://172.17.0.81:9000", host: "localhost"

leberknecht
  • 1,526
  • 15
  • 27
1

For anyone else getting

Nginx 403 error: directory index of [folder] is forbidden

when using index.php while index.html works perfectly and having included index.php in the index in the server block of their site config in sites-enabled

server {
    listen 80;

    # this path MUST be exactly as docker-compose php volumes
    root /usr/share/nginx/html;

    index index.php

    ...
}

Make sure your nginx.conf file at /etc/nginx/nginx.conf actually loads your site config in the http block...

http {

    ...

    include /etc/nginx/conf.d/*.conf;

    # Load our websites config 
    include /etc/nginx/sites-enabled/*;
}
myol
  • 8,857
  • 19
  • 82
  • 143
0

Using official images and nginxconfig

nginx-php.yaml (You need to add a port or network method to access nginx service)

services:
  nginx:
    image: nginx
    volumes:
      - /root/nginx/root:/usr/share/nginx/html:ro
      - /root/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - /root/nginx/templates:/etc/nginx/templates:ro
      - /root/nginx/log:/var/log/nginx

  php:
    image: php:fpm
    volumes:
      - /root/nginx/root:/usr/share/nginx/html:ro

example.com.conf.template

server {
    listen      80;
    listen      [::]:80;
    server_name example.com;
    set         $base /usr/share/nginx/html/example.com;
    root        $base/;

    # logging
    access_log  /var/log/nginx/access.log combined buffer=512k flush=1m;
    error_log   /var/log/nginx/error.log warn;

    # index.php
    index       index.php;

    # index.php fallback
    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    # handle .php
    location ~ \.php$ {
        fastcgi_pass                  php:9000;

        # 404
        try_files                     $fastcgi_script_name =404;

        # default fastcgi_params
        include                       fastcgi_params;

        # fastcgi settings
        fastcgi_index                 index.php;
        fastcgi_buffers               8 16k;
        fastcgi_buffer_size           32k;

        # fastcgi params
        fastcgi_param DOCUMENT_ROOT   $realpath_root;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        fastcgi_param PHP_ADMIN_VALUE "open_basedir=$base/:/usr/lib/php/:/tmp/";
    }
}
mnm
  • 1
  • 2