Search code examples
node.jsdockermeteorwindows-subsystem-for-linux

Why can't an app inside a docker container access it's own API endpoint?


I use the docker desktop app on Windows. Inside a Ubuntu WSL2 I have a docker container with a meteor / nodejs server running. Started via docker-compose from inside the WSL.

docker-compose.yaml
  ...
  environment:
    - ROOT_URL=http://localhost:8080
    - PORT=8080
  ports:
    - "8080:8080"

Call to Meteor-Files on the server, which will do the request:

  return new Promise((resolve, reject) => {
    UserFiles.load(url, options,
      (loadError, fileRef) => {
        if (loadError) {
          return reject(loadError);
        } else {
          return resolve(fileRef);
        }
      }, true);
  });
}

Now the app inside the docker container must access an HTTP endpoint of itself like http://127.0.0.1:8080/api

When I call the this endpoint from outside the docker, I get the correct result.

When I connect into the docker container with a terminal and curl the endpoint, I get the correct result.

Update: Also when running a Docker on a real Linux machine and not the WSL2 Ubuntu from Windows, it's working.

But, when the running app / service running inside the container tries to call itself by the same endpoint with the same URL, I get a ECONNREFUSED - CONNECTION REFUSED error.

Full Error Message:

FetchError: request to http://127.0.0.1:8080/api failed, reason: connect ECONNREFUSED 127.0.0.1:8080
at ClientRequest.<anonymous> (/app/bundle/programs/server/npm/node_modules/meteor/fetch/node_modules/node-fetch/lib/index.js:1444:11)
at ClientRequest.emit (events.js:315:20)
at ClientRequest.EventEmitter.emit (domain.js:483:12)
at Socket.socketErrorListener (_http_client.js:426:9)
at Socket.emit (events.js:315:20)
at Socket.EventEmitter.emit (domain.js:483:12)
at emitErrorNT (internal/streams/destroy.js:92:8)
at emitErrorAndCloseNT (internal/streams/destroy.js:60:3)
at processTicksAndRejections (internal/process/task_queues.js:84:21)
 => awaited here:
     at Function.Promise.await (/app/bundle/programs/server/npm/node_modules/meteor/promise/node_modules/meteor-promise/promise_server.js:56:12)
     at imports/startup/filemigration.js:273:20
     at /app/bundle/programs/server/npm/node_modules/meteor/promise/node_modules/meteor-promise/fiber_pool.js:43:40 {
   type: 'system',
   errno: 'ECONNREFUSED',
   code: 'ECONNREFUSED'
 }

What could be the problem of this? Maybe a WSL2 security problem?


Solution

  • From the error message, the code that ultimately needs to access an HTTP endpoint of the app itself, resides in a file called: imports/startup/filemigration.js

    From its name, I assume it runs when the app starts. However, there is a possibility that the Meteor app has not started its http Node server yet, hence the "connection refused" error message.

    There may even be a race condition between the different starting tasks, leading to different behaviors depending on the exact environments.

    You can try delaying your startup / migration task a bit, if feasible, to ensure the Meteor app has correctly started its server and listens on the port.

    You can typically use Meteor.startup(func) for that:

    Run [func] code when a client or a server starts.

    [...]

    On a server, the function will run as soon as the server process is finished starting.