node.jsdocker-api

How can I POSTto the Docker API using node.js http?


I have created a node.js express app that calls the Docker API on the unix socket /var/run/docker.sock

Anything involving a GET request works as expected. For anything involving a POST request, the action will get carried out, but then any future requests from my app to the Docker API are answered with 404 page not found.

For example, when I send a request to start a container, the container will start successfully, but everything sent to the Docker API after that errors out with 404.

Requests made outside of the app (like with curl) continue to work, so I'm sure it's something wrong in my code.

Here is the code that I suspect is causing a problem:

app.get('/containers/:containerId/start', (req, res) => {
  let containerId = req.params['containerId'];
  let apiOptions = requestOptions;
  apiOptions.path = `/containers/${containerId}/start`;
  apiOptions.method = 'POST';
  apiOptions.headers = {
    'Content-Type': 'application/json',
    'Content-Length': 0
  };
  let data = '';
  const apiReq = http.request(apiOptions, (apiRes) => {
    console.log(`${apiRes.statusCode} - ${apiOptions.path}`);
    apiRes.on('data', d => {
      data += d.toString();
    });
    apiRes.on('end', () => {
      res.setHeader("Content-Type", "application/json");
      res.send(JSON.stringify(data, null, 2));
    });
  });
  apiReq.on('error', err => {
    console.error(err)
  });
  apiReq.end();
});

It's pretty close to a textbook example and I have similar code doing GET requests without a problem. I am not using apiReq.write(data) for the POST because the Docker API expects the body to be empty.

I suspect there is something waiting for a connection to be closed and that I'm missing a line of code that would do that.

Here's a sample of some logging of the response codes for the various API requests and also extra logging info for the post request that causes the app to hang:

Listening on 0.0.0.0:8088.
200 - /images/json
200 - /containers/json?all="true"
{
  "socketPath": "/var/run/docker.sock",
  "path": "/containers/75b4ebdb7879e9cc9af134f63b4a02b2a218f52376fa377d89f5e384fdf78297/start",
  "method": "POST",
  "headers": {
    "Content-Type": "application/json",
    "Content-Length": 0
  }
}
204 - /containers/75b4ebdb7879e9cc9af134f63b4a02b2a218f52376fa377d89f5e384fdf78297/start
404 - /containers/json?all="true"
404 - /images/json

The 204 response from the POST indicates success according to the API Doc and I can see the container has started. But the app never recovers. I have to kill it and restart.

Any idea what I'm missing that's causing my app to hang on POST requests?

EDIT:

I have isolated the code that calls the Docker API in a separate program with a simple, 30 second setInterval() loop. Interestingly, it is working fine. I can stop the container (using a docker stop on command-line) and the POST request in my loop successfully starts it again.

Here's the code I'm testing with:

const http = require('http');

var containerId = '75b4ebdb7879e9cc9af134f63b4a02b2a218f52376fa377d89f5e384fdf78297';
var options = {
  socketPath: '/var/run/docker.sock',
  path: `/containers/${containerId}/start`,
  method: 'POST'
};

setInterval(() => {
  let data = '';
  const apiReq = http.request(options, (apiRes) => {
    apiRes.on('data', d => {
      data += d.toString();
    });
    apiRes.on('end', () => {
      console.log(`${apiRes.statusCode} - ${options.path}`);
      console.log(JSON.stringify(data, null, 2));
    });
  });
  apiReq.on('error', err => {
    console.error(err)
  });
  apiReq.end();
}, 30000);

Here's the console.log() output:

204 - /containers/75b4ebdb7879e9cc9af134f63b4a02b2a218f52376fa377d89f5e384fdf78297/start
""
304 - /containers/75b4ebdb7879e9cc9af134f63b4a02b2a218f52376fa377d89f5e384fdf78297/start
""
304 - /containers/75b4ebdb7879e9cc9af134f63b4a02b2a218f52376fa377d89f5e384fdf78297/start
""
204 - /containers/75b4ebdb7879e9cc9af134f63b4a02b2a218f52376fa377d89f5e384fdf78297/start
""

The 204 responses indicate the API successfully started the container. The 304 responses indicate there was nothing for the API to do, since the container was already running.

In short, the API call works fine outside of the express.js route I have the original code wrapped up in. I am now suspecting my problems lie with the way I have that configured.


Solution

  • I figured it out. The problem lies with this code:

      let apiOptions = requestOptions;
      apiOptions.path = `/containers/${containerId}/start`;
      apiOptions.method = 'POST';
      apiOptions.headers = {
        'Content-Type': 'application/json',
        'Content-Length': 0
      };
    

    Previously, I had declared requestOptions at the top of my program, like this:

    var requestOptions = {
      socketPath: '/var/run/docker.sock'
    };
    

    This was my mistake. I assumed, incorrectly, that let apiOptions = requestOptions was making a copy of requestOptions. I now believe it was creating apiOptions as a pointer to requestOptions.

    So, when I did apiOptions.method = 'POST', it was affecting not only apiOptions, but also the original requestOptions.

    I used similar logic with the GET API calls and, since GET is the default method, I never did `apiOptions.method = 'GET' in those API requests.

    Putting everything together, I believe that once I did the POST request, requestOptions.method was forever stuck as 'POST'. So any GET requests using a POST method would naturally fail.

    Here's what works:

    const dockerSocket = '/var/run/docker.sock';
    

    at the top of the program, instead of

    var requestOptions = {
      socketPath: '/var/run/docker.sock'
    };
    

    And,

      let apiOptions = {
        socketPath: dockerSocket,
        method: 'POST',
        path: `/containers/${containerId}/start`
      };
    

    in the call to the API.