Docker Compose. Call a custom script

2k views Asked by At

I have a simple docker-compose definition where I want to run a simple cmd/bash command:

basictests:
  image: ${DOCKER_REGISTRY-}mytests
  build:
    context: .
    dockerfile: MyTests/Dockerfile
  container_name: "mytestscontainer"
  command: 
    - echo "test"
  depends_on:
    dependent_env:
      condition: service_healthy 

note: It's a simplified test. Instead of calling echo, I actually want to run some more complicated scripts.

However, when I run this configuration, it throws with the following error:

 ==> /dev/null <==
 tail: cannot open 'echo "test"' for reading: No such file or directory

Not sure where tail has come from and how it can be fixed. Looking at this, it should work. If I remove command, it works fine. Any help will be appriciated.

UPDATE: DockerFile doesn't do anything other than installing dotnet and building my library.

UPDATE2: The base docker file is:

    FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
    WORKDIR /app
    EXPOSE 80
    EXPOSE 443

    FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
    WORKDIR /src
    # restore
    COPY ["MyTests/MyTests.csproj", "MyTests/"]
    RUN dotnet restore "MyTests/MyTests.csproj"
    # build
    COPY . .
    WORKDIR "/src/MyTests"
    RUN dotnet build "MyTests.csproj" -c Release -o /app/build

Adding suggested ENTRYPOINT ["/bin/bash", "-c"] to the end of above docker file doesn't help. note: I'm novice in docker and docker-compose.

UPDATE3: Adding ENTRYPOINT ["tail", "-f", "/dev/null"] to the end of docketfile and changing how I call command to command: [echo, test], doesn't help. In general, I'm curious what's the deal with tail? Why it appears in this case at all?

NOTE: for the context, I want to configure integration test that will work with few containers, so instead of echo .., I want to run dotnet test .. for my integration tests

UPDATE4: The below configuration:

    FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
    WORKDIR /src
    EXPOSE 80
    EXPOSE 443
    # restore
    COPY ["MyTests/MyTests.csproj", "MyTests/"]
    RUN dotnet restore "MyTests/MyTests.csproj"
    # build
    COPY . .
    WORKDIR "/src/MyTests"
    RUN dotnet build "MyTests.csproj" -c Release -o /app/build

    ENTRYPOINT ["/bin/bash"]

works well if I run it from cmd locally:

    %SomePath%\Demo>docker build -f MyTests/Dockerfile  -t mytest .
    [+] Building 0.3s (12/12) FINISHED
     => [internal] load build definition from Dockerfile
     => => transferring dockerfile: 32B
     => [internal] load .dockerignore
     => => transferring context: 35B
     => [internal] load metadata for mcr.microsoft.com/dotnet/sdk:6.0
     => [1/7] FROM mcr.microsoft.com/dotnet/sdk:6.0
     => [internal] load build context
     => => transferring context: 2.00kB
     => CACHED [2/7] WORKDIR /src 
     => CACHED [3/7] COPY [MyTests/MyTests.csproj, MyTests/] 
     => CACHED [4/7] RUN dotnet restore "MyTests/MyTests.csproj"
     => CACHED [5/7] COPY . .
     => CACHED [6/7] WORKDIR /src/MyTests
     => CACHED [7/7] RUN dotnet build "MyTests.csproj" -c Release -o /app/build
     => exporting to image
     => => exporting layers
     => => writing image sha256:9c...
     => => naming to docker.io/library/mytest

    %SomePath%\Demo>docker run -it mytest
    root@999afef78716:/src/MyTests# ls
    MyTests.csproj  ApiTests.cs  Properties  obj

So, it looks like the problem is only about how to call it correctly from docker-compose configuration. I've also tried setting the below options

..
image: ${DOCKER_REGISTRY-}mytests
stdin_open: true # docker run -i
tty: true        # docker run -t
..

with no luck.

UPDATE5: I've tried to simplify all logic I have to these 2 files with new console application:

Dockerfile:

 FROM alpine:3.14
 WORKDIR "/src"
 CMD ["/bin/bash"]

 # same behavior if I use `CMD ["/bin/sh"]`

docker-compose:

version: '3.4'

services:
  consoleappdocker:
    image: ${DOCKER_REGISTRY-}consoleappdocker
    build:
      context: .
      dockerfile: ConsoleAppDocker/Dockerfile
    container_name: "consoleappdocker"
    command: ["/bin/bash", "-c", "echo test"]

that still fails with tail: invalid number 'echo test' error, however I just noticed this output:

 docker ps -a  --no-trunc
 CONTAINER ID    IMAGE                  COMMAND                                        CREATED         STATUS                     PORTS     NAMES
 %id%            consoleappdocker:dev   "tail -f /dev/null /bin/bash -c 'echo test'"   7 minutes ago   Exited (1) 7 minutes ago             consoleappdocker

so it looks like @VonC is right, however I have no ideas where this tail part has been initially added

UPDATE6 : Apparently the tail part is added by visual studio that I used to build and run my application, I found it in the logs:

    docker-compose  -f "%PATH%\docker-compose.yml" -f "%PATH%\docker-compose.override.yml" -f "%PATH%\obj\Docker\docker-compose.vs.debug.g.yml" -p dockercompose14997374556887134345 --ansi never --profile "*" config
    name: dockercompose14997374556887134345
    services:
      consoleappdocker:
        build:
          context: %PATH%
          dockerfile: ConsoleAppDocker/Dockerfile
          labels:
            com.microsoft.created-by: visual-studio
            com.microsoft.visual-studio.project-name: ConsoleAppDocker
        command:
        - /bin/bash
        - -c
        - echo test
        container_name: consoletest
        entrypoint:
        - tail
        - -f
        - /dev/null
        environment:
    ..
        image: consoleappdocker:dev
        labels:
    ..
        networks:
          default: null
        tty: true
        volumes:
    ..
    networks:
      default:
        name: dockercompose14997374556887134345_default
    docker-compose  -f "%PATH%\docker-compose.yml" -f "%PATH%\docker-compose.override.yml" -f "%PATH%\obj\Docker\docker-compose.vs.debug.g.yml" -p dockercompose14997374556887134345 --ansi never build

quite unexpected case, but I think it's more or less clear what to do next

1

There are 1 answers

4
VonC On BEST ANSWER

The command directive in the docker-compose.yml file is used to override the default CMD specified in the Dockerfile.
The command directive is equivalent to the command you would pass after docker run <image> <command>.

You are seeing an error about tail because your Dockerfile is likely configured to run a command that involves tail at the end of its execution, and when you override the command in the docker-compose.yml file, the tail command is trying to read a file named echo "test", which does not exist.

In your case, you want to run a command inside the container. The docker-compose.yml should look like this:

    basictests:
      image: ${DOCKER_REGISTRY-}mytests
      build:
        context: .
        dockerfile: MyTests/Dockerfile
      container_name: "mytestscontainer"
      command: ["/bin/bash", "-c", "echo test"]
      depends_on:
        dependent_env:
          condition: service_healthy 

This will execute the command echo test inside the container.

For running complex commands or scripts, you can:

  1. Add the scripts to the Docker image during the build, and then call the script from the command directive.

  2. Add the script to the Docker image during the build, and call the script from the CMD or ENTRYPOINT in the Dockerfile.

  3. Mount a volume with the script to the container, and call the script from the command directive in the docker-compose.yml.

For your final note about running integration tests, you could add a script that runs your tests to the Docker image and then call that script in the command directive. The script could look something like this:

#!/bin/bash

# Run the tests
dotnet test MyTests/MyTests.csproj

Then in your docker-compose.yml:

    basictests:
      image: ${DOCKER_REGISTRY-}mytests
      build:
        context: .
        dockerfile: MyTests/Dockerfile
      container_name: "mytestscontainer"
      command: ["/bin/bash", "-c", "/path/to/your/script.sh"]
      depends_on:
        dependent_env:
          condition: service_healthy 

Do replace "/path/to/your/script.sh" with the actual path to your script in the Docker container.

And make sure your script have execution permissions. You can set these during the Docker build with the RUN directive:

COPY ./host/path/to/your/script.sh /path/to/your/script.sh
RUN chmod +x /path/to/your/script.sh

Do replace "./host/path/to/your/script.sh" with the path to your script on the host.


it's a good guess, but no I don't configure/see it anywhere

The Dockerfile you have provided does not show a CMD or ENTRYPOINT instruction that involves the tail command. However, the error message you are seeing indicates that the tail command is being run and is attempting to open 'echo "test"' as a file, which suggests that tail is being invoked somewhere.

It is also possible that tail is being run as part of the base image or a parent image, or by some process within the container. If the base image or parent image has a CMD or ENTRYPOINT that includes tail, and you are not overwriting it in your Dockerfile, that could explain why you are seeing the tail command in your error message.

One common use of tail -f /dev/null in a Dockerfile is to keep a container running indefinitely, even if its main process has terminated (as I did here). This is sometimes used in development environments or for services that need to stay up but might not always have a process to run.
If tail is being invoked in this way, trying to replace the tail -f /dev/null command with something else (like echo "test") could lead to the error message you are seeing.

To confirm this, you could examine the base or parent images, or any scripts or commands being run in the Dockerfile or the docker-compose.yml file.


When I applied docker-compose configuration with command command: ["/bin/bash", "-c", "echo test"], I see this error: tail: invalid number of bytes: 'echo test', which is still about tail...

If the ENTRYPOINT in your Dockerfile or in a base image is set to a tail command, and you are using the command directive in your docker-compose file to run bash -c "echo test", Docker will try to append the command to the ENTRYPOINT command.
In this case, it would try to execute something like tail bash -c "echo test", which is not a valid command and would cause the error you are seeing.


You find a similar "invalid number" error here, where the base image alpine does not have a bash, and where built-in commands don't have all the more common options.
Same idea in "BUSYBOX script is wrong"


Apparently the tail part is added by Visual Studio that I used to build and run my application, I found it in the logs:

docker-compose  -f "%PATH%\docker-compose.yml" -f "%PATH%\docker-compose.override.yml" -f "%PATH%\obj\Docker\docker-compose.vs.debug.g.yml" -p dockercompose14997374556887134345 --ansi never --profile "*" config
    name: dockercompose14997374556887134345
    services:
      consoleappdocker:
        build:
          context: %PATH%
          dockerfile: ConsoleAppDocker/Dockerfile
          labels:
            com.microsoft.created-by: visual-studio
            com.microsoft.visual-studio.project-name: ConsoleAppDocker
        command:
        - /bin/bash
        - -c
        - echo test
        container_name: consoletest
        entrypoint:
        - tail
        - -f
        - /dev/null
        environment:
    ..
        image: consoleappdocker:dev
        labels:
    ..
        networks:
          default: null
        tty: true
        volumes:
    ..
    networks:
      default:
        name: dockercompose14997374556887134345_default
    docker-compose  -f "%PATH%\docker-compose.yml" -f "%PATH%\docker-compose.override.yml" -f "%PATH%\obj\Docker\docker-compose.vs.debug.g.yml" -p dockercompose14997374556887134345 --ansi never build

You can see that practice mentioned before (VS 2019) and here.
As mentioned in "Azure Function in Docker Container runs in Visual Studio, but does nothing from command line":

The command you posted is only used for Visual Studios debugging functionality. It overwites the entrypoint with tail -f /dev/null which is basically "loop forever".

Official documentation:

The entry point is tail -f /dev/null, which is an infinite wait to keep the container running.
When the app is launched through the debugger, it is the debugger that is responsible to run the app (that is, dotnet webapp.dll).
If launched without debugging, the tooling runs a docker exec -i {containerId} dotnet webapp.dll to run the app.

To modify the container only for debugging, create a stage and then use the MSBuild property DockerfileFastModeStage to tell Visual Studio to use your customized stage when debugging. Refer to the Dockerfile reference in the Docker documentation for information about Dockerfile commands.

So check your project properties file for a section like:

<PropertyGroup>
     <!-- other property settings -->
     <DockerfileFastModeStage>debug</DockerfileFastModeStage>
</PropertyGroup>