How I (prefer to) automate Docker tasks with Java

Often times you want to automate tasks that involve the execution of docker commands. In my case, I had the need to automate docker images pulling and execution which parameters vary according to each specific test scenario.

Since the test scenario set up involved the processing of data and other tasks I wanted to automate, I opted for developing a desktop application with Java. The program should also be able to run docker processes with the application under test. In theory, running processes with Java should be pretty straightforward. Let’s have a look at how we would create and run a simple process.

The processBuilder class

The class ProcessBuilder offers methods to easily create and run processes in our machine. To create a process you need:

  • A Strings array with the command elements
  • An instance of ProcessBuilder implementing the methods of the abstract class Process
  • Run the process using the start() method 
  • An instance of BufferedReader (or your preferred input reader class) taking the process input stream to log its output

Let’s write those code lines. In this example let’s run a simple ls command to show the content of our root directory.

String[] commandElements = {"ls", “/"};
// create an instance of processBuilder passing the command as argument
ProcessBuilder pb = new  ProcessBuilder(commandElements);

// Start the process and create a Process instance 
Process lsCmd = pb.start();

// print the process output 
String logLine;
BufferedReader commandReader = new BufferedReader(new InputStreamReader(lsCmd.getInputStream()));
while ((logLine = commandReader.readLine()) != null) {
	System.out.println(logLine);
}

This code would output something like this:

Applications
Library
System
Users
Volumes
bin
cores
dev
etc
home
opt
private
sbin
tmp
usr
var

The catch with Docker

The catch with Docker and many other programs, is that their commands will not run automatically from your Java code as they would when you run them manually from your terminal. At least not without doing some tweaks. 

If we modify the commandElements array from the code above to have “docker pull” “debian”, nothing will happen. Why? Because docker is not in the environment PATH

If you don’t know what the variable PATH is, take it as a variable that encapsulates multiple paths to programs that you want to run via commands. It’s an environment variable and in your shell it has the path to docker and that’s why you can run docker commands from your terminal.

But here is the catch: our ProcessBuilder has a totally different PATH variable than the one you have in your shell session. The processes in our code inherit their parent process’ PATH. Since we run the code from an IDE and we lunch the IDE normally from our desktop, the process’ PATH will not have access to the same programs as our terminal because they have different settings. This happens not only with Docker but also with other programs.

The tweak

The fix didn’t seem obvious to me at first glance. I even came across the Spotify docker-client API but that seemed an over kill for what I needed. My preferred solution was rather to hack the process itself to enable it to use Docker. Here’s how it’s done:

  • Get the path to docker
  • Use the Map interface to get a collection of your process environment variables
  • Add the docker path to your environment PATH

The path to the docker application can be found by running which docker in your terminal. In my case docker is in /usr/local/bin/. Now, let’s write those code lines.

// initialise the array with the command elements
String[] commandElements = {“docker”, “pull”, “debian"};

// create an instance of process ProcessBuilder
ProcessBuilder pb = new  ProcessBuilder(commandElements);

/* get a collection of all
* environment vars of your process
*/
Map<String, String> env = pb.environment();

// access the PATH var and add the docker path
String newEnvPAth = env.get("PATH") + ":/usr/local/bin/";

// replace the process PATH with the modified PATH
env.replace("PATH", newEnvPAth);
Process lsCmd = pb.start();

And that should do the job.

Conclusion

Done! Now your docker tasks will be elegantly automated with Java. In my case I prefer to have a bash script with docker commands and execute the script from java. Whatever approach you choose if you don’t want to use third party libraries you need to be mindful of your system environment.

I hope you enjoyed the article. Happy coding!