While ramping up on Docker a while ago, I noticed that in this new world of containers and micro services a few things simply work differently from what I was used to. I wanted to take some time to summarise and explain these differences for people who are just getting started.

Don’t even try to call //localhost

Yes the container engine runs locally on your machine, and yes it will usually expose a few services against localhost.
This does not mean that you can call //localhost:OtherContainer’sPort from within the containerised application.

And quite frankly; why would it?

If we think about it logically, we notice that a container (at least in this respect) is not all too different from a virtual machine.
From the container’s perspective another container is a different object on the local network.

Docker resolves this problem by giving each container a hostname that you can use like so:

  • //myfirstcontainer/api/id
  • //mysecondcontainer/api/addItem

Don’t forget to build before you build

Depending on what programming language you use, you may need to create binaries before you can package them into a container.
For example: In .NET Core we still need to do a dot net restore and a dot net build, then select the binaries during our container build and place them correctly

Obviously there are programming languages that do not need a formal build step. For Node.JS for example we simply copy the package.json and do a package restore within the container before transferring the app specific files as well.

Luckily there is an example docker file available for almost any programming language.

Node.JS
.NET Core

Expect the unexpected

Never expect that one container will start before the other or that all containers will be up and ready to receive messages at the same time.

With information flowing primarily over REST, be sure to either write or use an existing service to manage the state of transactions.
If you can design a service to be stateless, then that is even better.

Another common design principle is to “embrace the exception”. Modern container orchestration engines will just spin up a new container, if one crashes, so it’s really not as much of a big deal anymore, if something goes wrong. (in principal anyway)

What is important, is that we can recover from a failure like the one described above. This is enabled by good transaction management, sensible timeouts and a reasonable retry strategy.