Connection Issues During Calls Between PHP Containers

Background

I’m relatively new to Docker and have finally sat down to learn more about it for a month or so. I am currently attempting to create a local development environment for my team, so ideally an environment that is easily scalable and is able to run multiple applications at the same time.

So far, my experiments have gone quite well, until I hit this particular roadblock, occurring when cross-application (and therefore cross-container) calls need to occur.

Codebase

I will provide the relevant code underneath this post, but for easier browsing, I have also set up a public repository replicating my issue: Tom Peters / Docker Workshop · GitLab

The Docker environment consists of a single NginX container and two PHP containers (“App” and “API”) containing Symfony-based applications, managed via a single Docker-Compose file, and all sharing the same network.

Intended Functionality

“App” should perform a HTTP-based request (using Symfony’s HttpClient) to “API”, retrieve a message, and display it on a simple page.

Issue At Hand

Currently, routing to the individual applications functions as intended and show the expected results.

However, when the “App” container attempts to request data from the “API” container, I run into one of several issues:

  • http://API/api/data results in a refused connection on port 80.
  • http://API:9000/api/data results in a connection reset by peer.
  • http:/host.docker.internal/api/data resolves, but simply retrieves the first matching route, so this setup would not allow for applications using the same routes.

Troubleshooting So Far

As the above shows, I have already attempted several different solutions besides the ones that have not been persisted to the example repository.

Beyond that, I have browsed several issues here on the forums, but none have seemed to offer the solutions I am seeking (sources: 1, 2, 3, 4, 5).

Relevant Code

Docker Configuration

version: '3'

services:
    server:
        container_name: NginX
        image: nginx:1.14.0
        tty: true
        ports:
            - '80:80'
        networks:
            - local-network
        volumes:
            - .\nginx:/etc/nginx/conf.d
            - .\api:/var/www/api
            - .\app:/var/www/app

    api:
        container_name: API
        image: php:8.1.3-fpm
        tty: true
        networks:
            - local-network
        volumes:
            - .\api:/var/www/api

    app:
        container_name: App
        image: php:8.1.3-fpm
        tty: true
        networks:
            - local-network
        volumes:
            - .\app:/var/www/app

networks:
    local-network:
        driver: bridge

NginX Configuration

API

server {
    server_name api.local;

    index index.php;
    root /var/www/api/public;

    access_log /var/log/nginx/api-access.log;
    error_log /var/log/nginx/api-error.log;

    location ~ \.php$ {
        fastcgi_split_path_info ^(.+\.php)(/.+)$;

        fastcgi_pass API:9000;
        fastcgi_index index.php;

        fastcgi_buffers 16 16k;
        fastcgi_buffer_size 32k;

        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }

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

App

server {
    server_name app.local;

    index index.php;
    root /var/www/app/public;

    access_log /var/log/nginx/app-access.log;
    error_log /var/log/nginx/app-error.log;

    location ~ \.php$ {
        fastcgi_split_path_info ^(.+\.php)(/.+)$;

        fastcgi_pass App:9000;
        fastcgi_index index.php;

        fastcgi_buffers 16 16k;
        fastcgi_buffer_size 32k;

        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }

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

PHP Setup

API Route

class DataController extends AbstractController
{
    /**
     * @Route("/api/data")
     */
    public function index(): Response
    {
        $today = new DateTime();

        return $this->json('The date and time are currently ' . $today->format('Y-m-d H:i:s'));
    }
}

App Route

class ExternalDataController extends AbstractController
{
    private HttpClientInterface $client;

    public function __construct(HttpClientInterface $client)
    {
        $this->client = $client;
    }

    /**
     * @Route("/")
     */
    public function index(): Response
    {
        /* Failed to connect to api port 80: Connection refused for "http://api/api/data". */
//        $response = $this->client->request('GET', 'http://API/api/data');

        /* Recv failure: Connection reset by peer for "http://api:9000/api/data". */
//        $response = $this->client->request('GET', 'http://API:9000/api/data');

        /* Resolves properly, but directs to the first /api/data found, which is not as intended. */
        $response = $this->client->request('GET', 'http://host.docker.internal/api/data');

        $message = $response->getContent();

        return $this->render('external_data/index.html.twig', [
            'message' => $message,
        ]);
    }

    /**
     * @Route("/api/data")
     */
    public function duplicateRoute(): Response
    {
        return $this->json('These are not the data you\'re looking for.');
    }