Configuring Applications With Docker

But it worked on my machine!I still remember when I was a young developer and someone introduced me to the environments where an application could be deployed: dev, test, acceptance, production. We as developers need to deal a lot with context, we deal with it on context switches, on variable scoping, and this is yet another context.

We need to code our application so it can modify its parameters or behaviour based on the place it’s running, this type of input has roughly three forms: arguments, files and environment variables.[1] For which Docker provides several alternatives to accommodate for these different inputs.
With Docker we can configure applications in two moments: during build time, or at runtime. The former includes the configuration right into the image, the latter is given when the container is instantiated.
The code for this blogpost can be found here.

Configuring At Build Time

Arguments

We can provide build arguments with the ARG statement inside the Dockerfile in combination with the –build-arg cli argument:

A message with the first argument will appear on the console, ignoring the others. The values given to these arguments only exist at build time, if you connect to the container you won’t be able to see them. This is quite useful for passwords or other sensitive information we need to provide when building the image but don’t want to persist inside the image.

Files

We can use either the COPY or ADD command to include files and folders inside images:

Both commands work similarly, but ADD decompresses tar files and allows to specify URLs (but not to decompress files coming from them).
After building the image we should have the files available in the container.

Configuring At Run Time

Arguments

Using the command ENTRYPOINT we can treat a docker container as a shell command:

FROM bash:4.4
ENTRYPOINT ["echo"]
CMD ["this a default message!"]

What this will do is invoke the echo program when we do docker run; CMD will act as default argument if we don’t provide any to Docker.

Environment variables are both available during build time and at runtime. It’s interesting to note that we can mix copying files at build time with environment variables at runtime, if you use Typesafe Config, you can copy files that refer to environment variables which values can be redefined on every execution.

Putting It All Together

Let’s see a more meaningful example, an image that compiles and runs a Maven project, a very simple Web application that shows pictures uploaded by the users.[2]

# Base on maven image to get java and javac available
FROM maven:3-jdk-8

First this project defines a couple of build arguments, useful for say, you want to fork this repository. Then we defined two environment variables, one defining the GitHub url from where to get the code and another as the standard JAVA_OPTS. After that we clone repository and compile it. The next step is copying the entrypoint and giving proper permissions to it’s usable when running the container. The final instructions define then entrypoint as the script copied in the step before with the default argument of 7000 (the port where the Http server will be listening).

If we go to our web browser and hit localhost:7000 we should be able to see our simple app.

Conclusion

I hope that with these very simple examples you could get an idea of how easy is to configure applications using Docker, and that it helps you avoid the mistake of pretending your machine is the only environment for your code has to run like I did.

[1] Programs can also take input from other means, such as sockets: for JVM processes you can use JMX, or expose an HTTP API.

[2] You wouldn’t do this in a real project, you would have at least two containers, one building your code and another running it. I decided to put everything into one to make things simpler.