Today I have released a first version of Fusio Plant which is an open source server management tool to easily self-host apps on your server, in this post I like to share some experiences of this process.
While building Plant, we had the goal to keep the host as small as possible, we basically only wanted to install Nginx and Docker
and start all projects through a simple docker-compose.yml
file. We also wanted to run the Plant app itself as docker container
so that it is easy to update the server panel itself. Moving the Plant app into a docker container means also that we can no
longer directly execute commands on the host. In general, this is a good thing, but for a server panel we need the option to change
i.e. the Nginx configuration or run Docker commands on the host.
Host Docker communication
In the development process, we had developed three versions to enable this Docker to host communication. These ideas are maybe also useful for other scenarios, so I will walk you through each version.
Cron
The first implementation used a simple cronjob which executed a bash script. The bash script walked through each file in an
input/
folder, executed each file and wrote the output to an output/
folder. Those input/
and output/
folders are also mounted into the docker container and inside the container we wrote a command into the
input/
folder and waited until the result was available at the output/
folder.
The biggest limitation of this solution was speed since cron can execute a script only once every minute. This means in the worst case, a user needs to wait almost a minute for the command to be executed.
inotifywait
To improve the performance we tried to use inotifywait
which is basically a tool which you can use to listen for
file changes inside a folder. The setup is basically identical to the cron version but instead of cron we use inotifywait
to listen for file changes inside the input/
folder and then write the result to the output/
folder.
To run this script we have also used supervisord to keep the bash script alive in case of errors.
Initially this has worked and improved the performance greatly, but unfortunately there are some scenarios where inotifywait
could not detect file changes in case the Docker app placed multiple files into the input/
folder.
Named pipe
As the last and final solution, we have changed the input folder to a named pipe. We can then mount this pipe into our Docker container and write events directly into this pipe. On the host we can then listen to this pipe and execute events directly on occurrence.
The bash script on the host can now easily listen for changes on the pipe s.
input=/opt/plant/input while true do while read -r line; do execute_command "$line"; done < $input sleep 1 done
To write events to the pipe, we can use basic file functions s.
$input = fopen('/tmp/input', 'w'); fwrite($input, '{"type": "my_command"}'); fclose($input);
After writing the command to the pipe we can directly check the /output
file and wait for
a response. To veriy that the script was fully executed we check for a specific --EOF-MARKER--
marker at the end of the file and if its available we stop reading the file.
$response = ''; $outputFile = '/tmp/output'; $output = fopen($outputFile, 'r'); $count = 0; while ($count < 32) { $size = filesize($outputFile); if ($size > 0) { $response.= fread($output, $size); } if (str_contains($response, '--EOF-MARKER--')) { break; } usleep(100_000); clearstatcache(); $count++; } fclose($output);
The clearstatcache
call in the snippet above is crucial since PHP automatically caches the responses
of the filesize
function, so we need to clear the cache on every iteration to get the live file size which
often changes on command execution.
Conclusion
As a conclusion, we have looked into three solutions to enable Docker host communication. In our case, the named pipe solution works perfectly since it is fast and lightweight, and we can now easily execute commands on the host system. In case you are interested in a new Docker-based server management tool, check out the Plant GitHub repository.