Search code examples
phpdockerftpcontainers

PHP FTP can't connect when containerized


I'm trying to containerize my PHP script.
But for some reason it's unable to connect to my FTP server when it's running in the container.
Running the script locally on my machine runs without any issues.

Dockerfile:

FROM php:8.1-cli

EXPOSE 8080

COPY . /var/www/html/

WORKDIR /var/www/html

ENTRYPOINT php -f serve.php

PHP Script:

$connection = ftp_connect(FTP_HOST, 21, 90);
ftp_pasv($connection, true);

if(ftp_login($connection, FTP_USERNAME, FTP_PASSWORD))
{
    $stream = fopen('data://text/plain,','r');
    ftp_fput($connection, $filename, $stream);
}

ftp_close($connection);

After 90 seconds it gives the following warning:
ftp_fput(): Connection timed out in /var/www/html/ftp.php on line 16

I tried bashing into the container and installing an FTP client. It gives me a similar timeout error - I can connect to the host, but running any commands results in a stall.

I also have tried running the container on a VPS to eliminate any local firewall/network issues. But the issue is the same.

Please let me know if any additional information is needed to solve the issue.


Solution

  • Mind the documentation of PHP's ftp_pasv function (emphasis mine):

    Please note that ftp_pasv() can only be called after a successful login or otherwise it will fail.

    Also note that you do not check the status return value of your ftp_pasv call, so you won't notice if that call actually succeeds (which is most likely won't). Because of that, your script will attempt to establish an active FTP connection. That won't work in a container (unless started with --network=host), because the container runs in a private network which is NATed by your host machine.

    Solution: Login first, enabling passive mode second (and also, always check your error return values; many of the older functions from the PHP standard library do not throw exceptions, but rely on error return values):

    if (ftp_login($connection, FTP_USERNAME, FTP_PASSWORD))
    {
        if (ftp_pasv($connection, true) === false) {
            throw new \Exception("could not enable passive mode")
        }
    
        $stream = fopen('data://text/plain,','r');
        ftp_fput($connection, $filename, $stream);
    }