7

I have some script I need to run during a Docker build which requires a tty (which Docker does not provide during a build). Under the hood the script uses the read command. With a tty, I can do things like (echo yes; echo no) | myscript.sh.

Without it I get strange errors I don't completely understand. So is there any way to use this script during the build (given that its not mine to modify?)

EDIT: Here's a more definite example of the error:

FROM ubuntu:14.04
RUN echo yes | read

which fails with:

Step 0 : FROM ubuntu:14.04
 ---> 826544226fdc
Step 1 : RUN echo yes | read
 ---> Running in 4d49fd03b38b
/bin/sh: 1: read: arg count
The command '/bin/sh -c echo yes | read' returned a non-zero code: 2
marius
  • 1,352
  • 1
  • 13
  • 29
  • 1
    Are you sure the absence of a tty is the problem? Per [this answer](http://unix.stackexchange.com/a/55522), the "read: arg count" error is due to the fact that the read builtin in /bin/sh requires an argument (variable in which to store the stdin reply) whereas /bin/bash does not (it uses $REPLY as a default). – nephtes Jul 22 '16 at 15:42
  • I posted the bounty, and to win it I'm looking for a solution to the problem at hand, not a 'you don't need that' answer. I know the original question 'can' be solved without tty, I want to know if it can be solved 'with' tty, as per the question. If we can all assume that the OP posted a dummy problem to demonstrate the issue, rather than the really complicated real problem that would be too long for this forum, then we will all get along fine. I know how to fix the specific sudo problem I was having, but I still need a tty for other things, such as when exec'ing into a running container... – Software Engineer Jul 25 '16 at 13:11
  • @EngineerDollery Do you have an example where you need a TTY during the build of your Dockerfile? The `docker exec` is not an example of that since you're not doing the build, and you have the `-t` option on that command. At present, the answer for the build is that it's not an option, and no one has provided an example to show it's even needed. – BMitch Jul 26 '16 at 01:34
  • Sudo is a good example. At my place we're using ansible for provisioning machines, including docker images, and as we have to use the same script for all machines we're not allowed to put any docker specific stuff in there, so I yum install ansible and run the appropriate playbook before unininstalling ansible again (ssh is out of the question). Ansible uses sudo, and sudo is broken (on centos at least) in that it requires tty (it's a well known bug). – Software Engineer Jul 26 '16 at 08:15
  • If all else fails, investigate [`expect`](http://expect.sourceforge.net/). – zwol Jul 28 '16 at 12:45
  • Your read error is not docker-related - it's because the read buitin requires at least one argument. Also, you cannot pipe into read as pipes create a fork, and read must have access to the current shell process memory. – Qualia Apr 16 '20 at 18:14
  • @MohsenSarkar, no, sorry :( I rarely need this, but when I do I have to find individual workarounds so as to not need it. – Software Engineer Mar 06 '22 at 10:00

4 Answers4

1

RUN <command> in Dockerfile reference:

shell form, the command is run in a shell, which by default is /bin/sh -c on Linux or cmd /S /C on Windows

let's see what exactly /bin/sh is in ubuntu:14.04:

$ docker run -it --rm ubuntu:14.04 bash
root@7bdcaf403396:/# ls -n /bin/sh
lrwxrwxrwx 1 0 0 4 Feb 19  2014 /bin/sh -> dash

/bin/sh is a symbolic link of dash, see read function in dash:

$ man dash
...
read [-p prompt] [-r] variable [...]
            The prompt is printed if the -p option is specified and the standard input is a terminal.  Then a line
            is read from the standard input.  The trailing newline is deleted from the line and the line is split as
            described in the section on word splitting above, and the pieces are assigned to the variables in order.
            At least one variable must be specified.  If there are more pieces than variables, the remaining pieces
            (along with the characters in IFS that separated them) are assigned to the last variable.  If there are
            more variables than pieces, the remaining variables are assigned the null string.  The read builtin will
            indicate success unless EOF is encountered on input, in which case failure is returned.

            By default, unless the -r option is specified, the backslash ``\'' acts as an escape character, causing
            the following character to be treated literally.  If a backslash is followed by a newline, the backslash
            and the newline will be deleted.
...

read function in dash:

At least one variable must be specified.

let's see read function in bash:

$ man bash
...
read  [-ers]  [-a aname] [-d delim] [-i text] [-n nchars] [-N nchars] [-p prompt] [-t timeout] [-u fd] [name...]
If  no names are supplied, the line read is assigned to the variable REPLY.  The return code is zero,
              unless end-of-file is encountered, read times out (in which case the  return  code  is  greater  than
              128), or an invalid file descriptor is supplied as the argument to -u.
...

So I guess your script myscript.sh is start with #!/bin/bash or something else but not /bin/sh.

Also, you can change your Dockerfile like below:

FROM ubuntu:14.04
RUN echo yes | read ENV_NAME

Links:

Shawyeok
  • 1,186
  • 1
  • 8
  • 15
1

Short answer : You can't do it straightly because docker build or either buildx didn't implement [/dev/tty, /dev/console]. But there is a hacky solution where you can achieve what you need but I highly discourage using it since it break the concept of CI. That's why docker didn't implement it.

Hacky solution

FROM ubuntu:14.04
RUN echo yes | read  #tty requirement command

As mentioned in docker reference document the RUN consist of two stage, first is execution of command and the second is commit to the image as a new layer. So you can do the stages manually on your own where we will provide tty to first stage(execution) and then commit the result.

Code:

  cd
  cat >> tty_wrapper.sh << EOF
  echo yes | read  ## Your command which needs tty
  rm /home/tty_wrapper.sh 
  EOF
  docker run --interactive --tty --detach --privileged --name name1 ubuntu:14.04
  docker cp tty_wrapper.sh name1:/home/
  docker exec name1 bash -c "cd /home && chmod +x tty_wrapper.sh && ./tty_wrapper.sh "
  docker commit name1 your:tag

Your new image is ready. Here is a description about the code. At first we make a bash script which wrap our tty to it and then remove itself after fist execute. Then we run a container with provided tty option(you can remove privileged if you don't need). Next step we copy wrapped bash script inside container and do the execution & commit stage on our own.

Mohsen Sarkar
  • 5,910
  • 7
  • 47
  • 86
0

You don't need a tty for feeding your data to your script . just doing something like (echo yes; echo no) | myscript.sh as you suggested will do. also please make sure you copy your file first before trying to execute it . something like COPY myscript.sh myscript.sh

Miad Abrin
  • 952
  • 7
  • 14
0

Most likely you don't need a tty. As the comment on the question shows, even the example provided is a situation where the read command was not properly called. A tty would turn the build into an interactive terminal process, which doesn't translate well to automated builds that may be run from tools without terminals.

If you need a tty, then there's the C library call to openpty that you would use when forking a process that includes a pseudo tty. You may be able to solve your problem with a tool like expect, but it's been so long that I don't remember if it creates a ptty or not. Alternatively, if your application can't be built automatically, you can manually perform the steps in a running container, and then docker commit the resulting container to make an image.

I'd recommend against any of those and to work out the procedure to build your application and install it in a non-interactive fashion. Depending on the application, it may be easier to modify the installer itself.

BMitch
  • 231,797
  • 42
  • 475
  • 450