skip to Main Content

I’m running a command line app from a Node.js process using spawn(). The process is launched with extra pipes in the stdio option. Here’s a simplified code sample:

const stdio = ['ignore', 'pipe', 'pipe', 'pipe', 'pipe'];
const process = spawn('/path/to/command', [], { stdio });

// Later...
const { 3: pipeWrite, 4: pipeRead } = process.stdio;
pipeRead.on('data', (data) => {
  if (String(data) === "PING?") {
    pipeWrite.write("PONG!");
  }
}); 

Now, this works fine but I want to run the command inside a docker container, using docker run as the spawned executable:

const stdio = ['ignore', 'pipe', 'pipe', 'pipe', 'pipe'];
const process = spawn(
  '/usr/bin/env', [
    'docker',
    'run',
    '--rm',
    'my-image',
    '/path/to/command'
  ], { stdio }
);

This fails, the command line app inside the docker container says it cannot write to pipe. Is it possible to achieve this with docker run?

I’ve set up a Github repo that demonstrates the problem, though I should make it clear that this is merely a demonstration and I do not have the means to change behaviour of the real child process (which is Chromium, in case anyone is interested!).

2

Answers


  1. EDITED: It seems docker ignores extraneous pipes, and the approach above does not work. What you could do instead, is create a shared volume, in which a stream/pipe is generated, and use it like you would want to.

    Be aware that it is generally advised to avoid piping large amounts of data in/out of docker, as this results in quite heavy log files. (Among other issues)

    Consider using a TCP socket from the host into the container instead

    Login or Signup to reply.
  2. The method you are trying isn’t achievable. A child process can share the file descriptors with parent however when running a process inside docker it is containerized first. You can achieve this using named pipes instead of pipes. Named pipes are like pipes but have the interface of a file.

    According to this it is doable using named pipes:

    This is the modified code of the Github repo you posted:

    test.js

    import { spawn } from "child_process";
    import path from 'path';
    import { fileURLToPath } from 'url';
    import {describe, jest} from '@jest/globals'
    import fs from "fs";
    
    const __dirname = path.dirname(fileURLToPath(import.meta.url));
    
    describe("on docker container", () => {
      let child;
      jest.setTimeout(60000);
      afterEach(() => child?.kill('SIGINT'));
      it("responds with PONG! when PING? is sent", async () => {
        child = spawn(
          "/usr/bin/env",
          [
            "docker",
            "run",
            "--rm",
            "--init",
            `-v=${ __dirname }:/usr/app`,
            "node:latest",
            "node",
            "/usr/app/ponger.js"
          ],
          {
            stdio: ['inherit', 'inherit', 'inherit']
          }
        );
    
        await new Promise((resolve, reject) => {
          const fifoWritePath = path.resolve(`${ __dirname }/named_pipe_in`);
          const fifoReadPath = path.resolve(`${ __dirname }/named_pipe_out`);
          const pipeWrite = fs.createWriteStream(fifoWritePath);
          const pipeRead = fs.createReadStream(fifoReadPath);
    
          pipeRead.on('data', (message) => {
            console.log(message)
            if (String(message) === "PONG!n") {
              resolve();
            }
            else {
              reject();
            }
          });
          pipeWrite.write("PING?n");
        });
      });
    });
    

    ponger.js

    import fs from "fs";
    import path from 'path';
    
    const fifoReadPath = path.resolve('/usr/app/named_pipe_in');
    const fifoWritePath = path.resolve('/usr/app/named_pipe_out');
    
    const pipeRead = fs.createReadStream(fifoReadPath);
    const pipeWrite = fs.createWriteStream(fifoWritePath);
    
    pipeRead.on("data", (message) => {
      if (String(message) === "PING?n") {
        pipeWrite.write("PONG!n");
      }
    });
    

    Before you run npm test while in the directory of the project execute the following :

    mkfifo --mode=777 named_pipe_out
    mkfifo --mode=777 named_pipe_out
    

    The previledges are only for test purposes, keep in mind that such a setting can ruin the security of your application.

    PS. I can elaborate further on why your approach will not going to work in this setting.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search