Lessons learned from building a Docker-based server panel

posted by chriskapp on

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.

dockerself-hostserverpanel