Sign Up

Sign Up to our social questions and Answers Engine to ask questions, answer people’s questions, and connect with other people.

Have an account? Sign In

Have an account? Sign In Now

Sign In

Login to our social questions & Answers Engine to ask questions answer people’s questions & connect with other people.

Sign Up Here

Forgot Password?

Don't have account, Sign Up Here

Forgot Password

Lost your password? Please enter your email address. You will receive a link and will create a new password via email.

Have an account? Sign In Now

You must login to ask question.

Forgot Password?

Need An Account, Sign Up Here

Please briefly explain why you feel this question should be reported.

Please briefly explain why you feel this answer should be reported.

Please briefly explain why you feel this user should be reported.

Sign InSign Up

StackOverflow Point

StackOverflow Point Navigation

  • Web Stories
  • Badges
  • Tags
Search
Ask A Question

Mobile menu

Close
Ask a Question
  • Web Stories
  • Badges
  • Tags
Home/ Questions/Q 245324
Next
Alex Hales
  • 0
Alex HalesTeacher
Asked: August 17, 20222022-08-17T03:20:26+00:00 2022-08-17T03:20:26+00:00In: Docker

How to run shell script on host from docker container?

  • 0

[ad_1]

This answer is just a more detailed version of Bradford Medeiros’s solution, which for me as well turned out to be the best answer, so the credit goes to him.

In his answer, he explains WHAT to do (named pipes) but not exactly HOW to do it.

I have to admit I didn’t know what are named pipes at the time I read his solution. So I struggled to implement it (while it’s actually really simple), but I did succeed.
So the point of my answer is just detailing the commands you need to run in order to get it working, but again, credit goes to him.

On the main host, chose the folder where you want to put your named pipe file, for instance /path/to/pipe/ and a pipe name, for instance mypipe, and then run:

mkfifo /path/to/pipe/mypipe

The pipe is created.
Type

ls -l /path/to/pipe/mypipe 

And check the access rights start with “p”, such as

prw-r--r-- 1 root root 0 mypipe

Now run:

tail -f /path/to/pipe/mypipe

The terminal is now waiting for data to be sent into this pipe

Now open another terminal window.

And then run:

echo "hello world" > /path/to/pipe/mypipe

Check the first terminal (the one with tail -f), it should display “hello world”

On the host container, instead of running tail -f which just outputs whatever is sent as input, run this command that will execute it as commands:

eval "$(cat /path/to/pipe/mypipe)"

Then, from the other terminal, try running:

echo "ls -l" > /path/to/pipe/mypipe

Go back to the first terminal and you should see the result of the ls -l command.

You may have noticed that in the previous part, right after ls -l output is displayed, it stops listening for commands.

Instead of eval "$(cat /path/to/pipe/mypipe)", run:

while true; do eval "$(cat /path/to/pipe/mypipe)"; done

(you can nohup that)

Now you can send unlimited number of commands one after the other, they will all be executed, not just the first one.

The only caveat is if the host has to reboot, the “while” loop will stop working.

To handle reboot, here what I’ve done:

Put the while true; do eval "$(cat /path/to/pipe/mypipe)"; done in a file called execpipe.sh with #!/bin/bash header

Don’t forget to chmod +x it

Add it to crontab by running

crontab -e

And then adding

@reboot /path/to/execpipe.sh

At this point, test it: reboot your server, and when it’s back up, echo some commands into the pipe and check if they are executed.
Of course, you aren’t able to see the output of commands, so ls -l won’t help, but touch somefile will help.

Another option is to modify the script to put the output in a file, such as:

while true; do eval "$(cat /path/to/pipe/mypipe)" &> /somepath/output.txt; done

Now you can run ls -l and the output (both stdout and stderr using &> in bash) should be in output.txt.

If you are using both docker compose and dockerfile like I do, here is what I’ve done:

Let’s assume you want to mount the mypipe’s parent folder as /hostpipe in your container

Add this:

VOLUME /hostpipe

in your dockerfile in order to create a mount point

Then add this:

volumes:
   - /path/to/pipe:/hostpipe

in your docker compose file in order to mount /path/to/pipe as /hostpipe

Restart your docker containers.

Exec into your docker container:

docker exec -it <container> bash

Go into the mount folder and check you can see the pipe:

cd /hostpipe && ls -l

Now try running a command from within the container:

echo "touch this_file_was_created_on_main_host_from_a_container.txt" > /hostpipe/mypipe

And it should work!

WARNING: If you have an OSX (Mac OS) host and a Linux container, it won’t work (explanation here https://stackoverflow.com/a/43474708/10018801 and issue here https://github.com/docker/for-mac/issues/483 ) because the pipe implementation is not the same, so what you write into the pipe from Linux can be read only by a Linux and what you write into the pipe from Mac OS can be read only by a Mac OS (this sentence might not be very accurate, but just be aware that a cross-platform issue exists).

For instance, when I run my docker setup in DEV from my Mac OS computer, the named pipe as explained above does not work. But in staging and production, I have Linux host and Linux containers, and it works perfectly.

Here is how I send a command from my Node.JS container to the main host and retrieve the output:

const pipePath = "/hostpipe/mypipe"
const outputPath = "/hostpipe/output.txt"
const commandToRun = "pwd && ls-l"

console.log("delete previous output")
if (fs.existsSync(outputPath)) fs.unlinkSync(outputPath)

console.log("writing to pipe...")
const wstream = fs.createWriteStream(pipePath)
wstream.write(commandToRun)
wstream.close()

console.log("waiting for output.txt...") //there are better ways to do that than setInterval
let timeout = 10000 //stop waiting after 10 seconds (something might be wrong)
const timeoutStart = Date.now()
const myLoop = setInterval(function () {
    if (Date.now() - timeoutStart > timeout) {
        clearInterval(myLoop);
        console.log("timed out")
    } else {
        //if output.txt exists, read it
        if (fs.existsSync(outputPath)) {
            clearInterval(myLoop);
            const data = fs.readFileSync(outputPath).toString()
            if (fs.existsSync(outputPath)) fs.unlinkSync(outputPath) //delete the output file
            console.log(data) //log the output of the command
        }
    }
}, 300);

[ad_2]

  • 0 0 Answers
  • 7 Views
  • 0 Followers
  • 0
Share
  • Facebook
  • Report
Leave an answer

Leave an answer
Cancel reply

Browse

Sidebar

Ask A Question

Related Questions

  • xcode - Can you build dynamic libraries for iOS and ...

    • 0 Answers
  • bash - How to check if a process id (PID) ...

    • 3 Answers
  • database - Oracle: Changing VARCHAR2 column to CLOB

    • 5 Answers
  • What's the difference between HEAD, working tree and index, in ...

    • 4 Answers
  • Amazon EC2 Free tier - how many instances can I ...

    • 0 Answers

Stats

  • Questions : 43k

Subscribe

Login

Forgot Password?

Footer

Follow

© 2022 Stackoverflow Point. All Rights Reserved.

Insert/edit link

Enter the destination URL

Or link to existing content

    No search term specified. Showing recent items. Search or use up and down arrow keys to select an item.