javaamazon-web-servicesdockeramazon-ec2ftp-server

FTP upload file failed in Docker container


I have a spring boot app using library apache commons-net
I have a FTP server running on AWS EC2.
Filezilla connect and upload file normally.
Local app connect and upload file normally.
When i run this app using Docker, the app connect to ftp succeed,but it cannot upload file.
Reply code is: 500

Dockerfile:

FROM openjdk:11
RUN mkdir /app
COPY build/libs/aws-batch1-1.0.0.jar /app/aws-batch1.jar
ENTRYPOINT ["java","-jar","/app/aws-batch1.jar","Test"]

Image build command:

docker build -t aws-batch1 .

Image run command:

docker run aws-batch1

Code connect FTP:

private boolean connectFTPServer() throws IOException {
        LOGGER.info("CONNECT");
        boolean flg = true;
        try {
            ftpClient.connect(FTP_SERVER_ADDRESS, FTP_SERVER_PORT_NUMBER);
            ftpClient.login(FTP_USERNAME, FTP_PASSWORD);
        } catch (IOException e) {
            LOGGER.error("CONNECT FAILED", e);
            flg = false;
        }

        int replyCode = ftpClient.getReplyCode();
        LOGGER.info("replyCode: " + replyCode);
        if (!FTPReply.isPositiveCompletion(replyCode)) {
            flg = false;
        }
        if (!flg) {
            throw new IOException();
        }
        return flg;
    }

Code upload file:

public boolean uploadFile(byte[] content, String fileName)
            throws IOException {
        boolean flg = false;
        LOGGER.info("uploadFile");
        if (connectFTPServer()) {
            LOGGER.info("connectFTPServer success");
            LOGGER.info("content: " + new String(content));
            try (InputStream is = new ByteArrayInputStream(content)) {
                ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
                boolean storeFile = ftpClient.storeFile(fileName, is);
                LOGGER.info("storeFile: " + storeFile);
                LOGGER.info("ftpClient.getReplyCode(): " + ftpClient.getReplyCode());

                flg = storeFile &&
                        ftpClient.getReplyCode() ==
                                FTPReply.CLOSING_DATA_CONNECTION;
                if (!flg) {
                    throw new IOException("FTP error");
                }
            } finally {
                closeConnectionFTPClient();
            }
        }
        return flg;
    }

Log when upload on local successful:

uploadFile
CONNECT
replyCode: 230
connectFTPServer success
content: test
storeFile: true
ftpClient.getReplyCode(): 226

Log when upload on Docker failed:

uploadFile
CONNECT
replyCode: 230
connectFTPServer success
content: test
storeFile: false
ftpClient.getReplyCode(): 500

Please help me. Am I missing any steps?

UPDATE:Thanks @Metin I have updated the code to connect to ftp client as below:

private boolean connectFTPServer() throws IOException {
        LOGGER.info("CONNECT");
        boolean flg = true;
        try {
            ftpClient.connect(FTP_SERVER_ADDRESS, FTP_SERVER_PORT_NUMBER);
            ftpClient.login(FTP_USERNAME, FTP_PASSWORD);
            ftpClient.enterLocalPassiveMode();
        } catch (IOException e) {
            LOGGER.error("CONNECT FAILED", e);
            flg = false;
        }

        int replyCode = ftpClient.getReplyCode();
        LOGGER.info("replyCode: " + replyCode);
        if (!FTPReply.isPositiveCompletion(replyCode)) {
            flg = false;
        }
        if (!flg) {
            throw new IOException();
        }
        return flg;
    }

and it works!


Solution

  • You might want to read about the differences of active and passive mode ftp: What is the difference between active and passive FTP?

    You should come to the conclusion that active mode ftp is not suited for containerized ftp clients, due to the nature that the server activily tries to initate a data connection to the ftp client, which is not possible for a container (unless the container uses network: host, which is typicaly unwanted).

    With passive mode ftp on the other hand, the server will tell your ftp client which port needs to be used for the next data connection.