42

I would like to know how to change the following behavior. Let's say my terminal has 28 lines. Then I use the following commands:

$ tput lines # my terminal
28
$ docker run  --rm  -it ubuntu:16.04 tput lines  # docker container
24  ## WHY??
$ docker run  --rm  -it ubuntu:16.04 bash # docker container inside command
root@810effa2777c:/# tput lines
28

As you can see, even when all the results should be 28, when I'm calling the container as docker run --rm -it ubuntu:16.04 tput lines it always gives me 24 despite the size of my terminal. This is not only with the ubuntu container, I also tried with debian (docker run --rm -it debian tput lines) and I'm having the same result 24.

The purpose of this is to use the mdp presentation tool which takes into account the lines in your terminal. When my implementation failed, I tried some other person's docker implementation but I ran to the same error.

Here's my error in an image:

Docker number of lines in terminal changing inside docker

Does anyone has any idea what it could be and how can this be solved?

silgon
  • 6,890
  • 7
  • 46
  • 67

6 Answers6

45

UPDATE

you can now install goinside command line tool with:

sudo npm install -g goinside

and go inside a docker container with a proper terminal size with:

goinside docker_container_name

Logic behind goinside

thanks to @VonC answer we've got a solution for this problem with a simple bash snippet that we put in ~/.profile:

goinside(){
    docker exec -it $1 bash -c "stty cols $COLUMNS rows $LINES && bash";
}
export -f goinside

now you are able to get inside a docker container without terminal size issues with:

$ goinside containername


remember to source ~/.profile before using the goinside function.


enabling autocompletion in bash

(as it's shared in one of comments below) if you want to enable autocompletion for goinside you can use this snippet in .profile:

goinside(){
    docker exec -it $1 bash -c "stty cols $COLUMNS rows $LINES && bash";
}
_goinside(){
    COMPREPLY=( $(docker ps --format "{{.Names}}" -f name=$2) );
}
complete -F _goinside goinside;
export -f goinside;

enabling autocompletion in zsh

if you are using zsh as your default terminal you can use this snippet inside your ~/.zshrc file:

autoload bashcompinit
bashcompinit
goinside(){
    docker exec -it $1 bash -c "stty cols $COLUMNS rows $LINES && bash";
}
_goinside(){
    COMPREPLY=( $(docker ps --format "{{.Names}}" -f name=$2) );
}
complete -F _goinside goinside;
export goinside;
Soorena
  • 4,352
  • 5
  • 30
  • 42
  • 2
    Also you can make completion. `_goinside(){ COMPREPLY=( $(docker ps --format "{{.Names}}" -f name=$2) ) }` and then export it `complete -F _goinside goinsid` – Shiplu Mokaddim Mar 16 '18 at 11:13
19

Update Sept. 2018: check if docker 18.06 has the same issue (it should not, after moby/moby issue 33794, and also moby/moby issue 35407 and PR 37172, part of the 18.06 release notes).


2016:

The Ubuntu Dockerfile includes:

CMD ["/bin/bash"]

That means the default ENTRYPOINT is sh -c (and I doubt tput line works well in a sh session, since tput uses terminfo database, which might be set only for bash in that image)

You could try overwrite ENTRYPOINT with bash -c and check if that works better.

That does not work from command line though:

docker run --entrypoint /bin/bash --rm  -it ubuntu:16.04 -i -c 'tput lines'
24

I will check the option of defining a custom image.

FROM ubuntu:16.04
ENTRYPOINT ["/bin/bash", "-c"]

The result is the same though:

docker run --rm  -it u 'tput lines'
24

This however "works":

FROM ubuntu:16.04
ENTRYPOINT [ "/bin/bash" ]

With:

docker@default:/c/Users/vonc/prog/testsu$ docker run --rm  -it u -i -c 'ls; tput lines'
bin  boot  dev  etc  home  lib  lib64  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
48

There might be a synchronization issue, as the same command does return 24 from time to time.

Actually, the following always return "not 24" with:

FROM ubuntu:16.04
ENTRYPOINT [ "/bin/bash", "-l", "-i", "-c" ]

docker run --rm  -it u -c 'sleep 0.1; ls; tput lines'
48

The OP silgon proposes in the comments:

docker run --rm -it --entrypoint /bin/bash ubuntu:16.04 -c "sleep 0.1 && tput lines"

As BMitch comments below:

Given the success of sleep my suspicion is that docker spins up the container with the running command, and once up, the client attaches to the running container. Typically something that takes milliseconds.

That gave me another idea:

docker@default:/c/Users/vonc/prog/testsu$ 
docker run --entrypoint='/bin/bash' --name ub -d -it ubuntu:16.04
  0d9b8783afbb5e3ff4232da071d3f357985351ea1ce4d142bf6617ac456fb76b
docker@default:/c/Users/vonc/prog/testsu$ 
d attach ub
  root@0d9b8783afbb:/# tput lines
  48
  root@0d9b8783afbb:/# exit
exit
docker@default:/c/Users/vonc/prog/testsu$ drmae
0d9b8783afbb5e3ff4232da071d3f357985351ea1ce4d142bf6617ac456fb76b

A tput lines within an attached session works just fine.
(On the drmae alias, see "How to remove old and unused Docker images")


thajeztah adds in the comments:

the container is created, then started with the defaults (80x24), and after that (when -it), a session is attached.
The session is specifying the size of the terminal;

See "Resize a container TTY" API.

 DEBU[0244] Calling POST /v1.25/containers/c42fd5c4eb79c06fd7f9912b8359022f7d93887afbb33b57a67ed8bb7bfee4‌​3a/resize?h=46&w=221 

For more, see docker issue 25450.
It is related to issue 10341 "Container create or start should accept height/width params". Aleksa Sarai (cyphar) adds (Sept. 2016):

This has actually popped up again inside the runtime-spec (opencontainers/runtime-spec PR 563).
Basically, since Windows requires the ability to set the console size on first start, we might end up adding it for all platforms.


The OP silgon points out to the code in api/client/container/run.go:

// Telling the Windows daemon the initial size of the tty during start makes
// a far better user experience rather than relying on subsequent resizes
// to cause things to catch up.
if runtime.GOOS == "windows" {
    hostConfig.ConsoleSize[0], hostConfig.ConsoleSize[1] = dockerCli.GetTtySize()
}

With the logical question:

would it make sense to use this property on Linux as well, and set the initial console size using that value?

Kenfe-Mickaël Laventure (mlaventure) is on it, and a new patch could make it to Docker 1.13.

VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
  • Your command is not working (at least not for me), it throws me the following error `docker: Error response from daemon: /bin/bash -c: no such file or directory` which is weird. However, based on your command I figured I could use `docker run --rm -it ubuntu:16.04 bash -c "tput lines"`, thus I change `sh -c` to `/bin/bash -c++ as you said. This command runs, but it also gives 24 as the output as I specified in my question. – silgon Aug 08 '16 at 09:26
  • @silgon I leave that answer for now, as I think the main difference between your commands is the bash session. However, even with a custom image, tput still returns 24 – VonC Aug 08 '16 at 09:33
  • @silgon There seems to be a synchronization issue: Try *severl times* the last command I mention in my (edited) answer: most of the time, it does not return 24. – VonC Aug 08 '16 at 11:01
  • 2
    @silgon Actually, with a sleep in front of the tput command, the result is *never* 24. – VonC Aug 08 '16 at 11:05
  • 1
    Given the success of `sleep` my suspicion is that docker spins up the container with the running command, and once up, the client attaches to the running container. Typically something that takes milliseconds. – BMitch Aug 08 '16 at 11:23
  • Just try it. Working perfectly well, thanks! Now I know how to troubleshoot it for other applications. Can you maybe change your command to `docker run --rm -it --entrypoint /bin/bash ubuntu:16.04 -c "sleep 0.1 && tput lines"`. Thus it's a generic answer and it doesn't depend on the container you created. I'm accepting your answer already. =) BTW. @VonC according to the system I still need to wait 18h to award you the bounty. I tried and it didn't let me. Thanks again! ;) – silgon Aug 08 '16 at 11:37
  • @BMitch Thank you for the comment: I have included it as an annex in my answer for more visibility. I am not clear on why the command would be executed before the container is fully attached though. – VonC Aug 08 '16 at 11:41
  • Ok, thanks @VonC, I'll award the bounty tomorrow when the system allows me to do it. Quite an interesting problem in the end as BMitch commented, about how the command is run before the container being fully attached. – silgon Aug 08 '16 at 11:44
  • @VonC at the api level, what's likely happening is a "create", then a "start" (those two together make up "run), then an "attach". You can't attach to a container that isn't running yet and it's extra effort to make that a single atomic operation. – BMitch Aug 08 '16 at 11:44
  • @BMitch does that mean the command is executed after create, but before start? – VonC Aug 08 '16 at 11:50
  • @vonc, my suspicion is that the command runs when you execute "start" but doesn't get the tty from the client until the "attach" completes. – BMitch Aug 08 '16 at 11:53
  • 1
    I think @vonc's conclusion is correct; the container is *created*, then *started* with the defaults (80x24), and after that (when `-it`), a session is attached. The *session* is specifying the size of the terminal; `DEBU[0244] Calling POST /v1.25/containers/c42fd5c4eb79c06fd7f9912b8359022f7d93887afbb33b57a67ed8bb7bfee43a/resize?h=46&w=221` – thaJeztah Aug 08 '16 at 12:01
  • @thaJeztah Good point, referring to the Resize TTY API (https://docs.docker.com/engine/reference/api/docker_remote_api_v1.24/#/resize-a-container-tty). I have included your comment in the answer for more visibility. – VonC Aug 08 '16 at 12:15
5

A nice way to run bash inside the container without encountering line problems is here:

docker exec -e COLUMNS="`tput cols`" -e LINES="`tput lines`" -ti container bash
NotSoShabby
  • 3,316
  • 9
  • 32
  • 56
1

The comments about sh versus terminfo are largely irrelevant. The relevant part (not clear in the given answer) is the way the command is executed. tput checks three features in the following order (using setupterm):

  1. the size of the terminal from the terminfo database (many descriptions do not give this information, but with TERM=xterm, it is 24 by 80),
  2. the actual number of lines if it can get that information from the operating system (i.e., the current window size), and
  3. the LINES and COLUMNS environment variables.

A command which is run without an interactive shell could be executed in a way that precludes getting the current window size. For example, that is a feature of ssh (the -t option). Also, it would be possible (though pointless) for Docker to set the LINES and COLUMNS variables.

Either case (1) or (3) is enough to explain the behavior; introducing time-delays and races does not do that.

Thomas Dickey
  • 51,086
  • 7
  • 70
  • 105
  • I have udated my answer: https://github.com/opencontainers/runtime-spec/pull/563 will take care of that for all platforms. – VonC Sep 20 '16 at 13:01
1

It has been fixed in docker 18.06: https://github.com/moby/moby/issues/33794#issuecomment-406814439

Nico Toub
  • 1,528
  • 15
  • 17
1

I just tested with version Docker version 18.06.1-ce, build e68fc7a. It seems to have the same problem. However, one of the guys in the github issue gave a practical workaround:

docker run --rm -it -e COLUMNS=$COLUMNS -e LINES=$LINES -e TERM=$TERM -it ubuntu:16.04 tput lines
silgon
  • 6,890
  • 7
  • 46
  • 67