Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ


Choose your language

InfoQ Homepage Articles Containers Are Contagious and Often Misused

Containers Are Contagious and Often Misused

Key Takeaways

  • Containers are not the only solution for the write-once run-everywhere objective, you can use cross-platform frameworks to build clean/simple solutions that are also easily deployable between different environments/clouds.
  • The initial effort of learning how to develop, deploy, and run containerized applications creates a sunk cost effect that makes it hard to move out of containers when needed (the Concorde fallacy). 
  • Using a modern technology stack can liberate you from Kubernetes. Using PaaS and Serverless gives you more time to focus on your application needs.
  • Stacking layers of abstractions affects performance. Using low-level programming language with containers can give you performance gains that can be crucial for some applications.
  • Simplicity is vital in maintaining solutions with ever more complex and more demanding requirements. Containers can simplify some solutions but are often an extra layer of unintuitive complexity.

[My writings are personal opinions and don’t represent my company]

Let’s get something straight right at the beginning — this article is not to argue that containers are bad, containers are certainly one of many great options developers have in their hands today. This article is also not scoped at the pros/cons of containers, my intent is just to present the developers and dev leads with some considerations around containers that I believe should be on the table when containers are discussed.

Before we dive in let me take your temperature (just kidding, I hate COVID), let’s just make sure you live in 2021 -

  •  .Net 5 is released already and (like its predecessor .Net Core) is cross-platform (Windows, Linux, and macOS).
  • OpenAPI, OpenID, OAuth2 are dominant open standards (among many others). E.g. the OpenID Foundation mentions that there are ‘over one billion OpenID enabled user accounts and over 50,000 websites accepting OpenID for logins’.
  • Most major clouds (including Azure) natively support both Linux and Windows operating systems, and natively supports Java as much as they support C# (among other languages). In fact, Java is so popular inside Microsoft that it got its own open-source Java SDK – OpenJDK.
  • There are clean solutions today to do one infrastructure-as-code for multi-cloud.

If these points are old news to you that is great, otherwise I’m afraid you’re still living in 2010 when containers were the only viable solution for many issues and long-term symptoms didn’t show up yet. If this is the case it’s fine, I trust you have it in you to be open-minded to what is coming.

Containers are not the only solution for the ‘write-once run-everywhere’ objective

Ten years ago it was the dream of every developer to pull a code from a repo and hit F5 to run it without running into some dependency issue that broke the build; deploying the code to new environments was cumbersome and error-prone. containers are excellent at resolving these challenges but are not the only option in 2021.

(I know this discussion is for developers not for business leads but let’s be real, just five years ago if the business folks tell you they need an ‘exit-strategy’ they were pretty much telling you to containerize your apps)

As the world moves towards cross-platform frameworks and open standards, code deployment between different environments/clouds is becoming easier by day — your app no longer needs to live in a box to get the benefits of container deployments. Take advantage of these cross-platform technologies and build upon open standards. Here are few examples:

  • Rebus allows you to write against an emulated queue during development and later deploy the code against an actual queue hosted in any supported platform (they support multiple services like Azure Service Bus, RabbitMQ, Amazon SQS, etc.).
  • Like Rebus, abstracts away the storage, it supports Azure Blob, AWS S3 among many others.
  • GitHub Actions allows you to deploy your same codebase directly into an Azure and/or AWS web app or serverless offerings if you use a language supported by both (like C#, Java, Node.js..etc).

If/when you can’t use a modern approach and the requirements ask for containers consider using a low(er)-level language (C++/Rust...etc.) – why bother with an additional abstraction layer (i.e. runtime) and possibly compromise performance if portability is a no issue? Am I saying we should forget C# and Java and start writing Rust? It is actually not a bad idea when optimal performance is required, the application requirements should inform your decision but suffice to say that stacking layers of abstractions even when it doesn’t cost much in terms of performance it is certainly not free.

Containers are contagious

Imagine you have a scenario where containers make perfect sense (there are many use cases where this is still the case today), you containerized your app, now imagine you get a second app you want to write, do you go back to the drawing board and check what would be the best solution for this second app or you use your already existing skill set of hosting/running containers and leverage all the knowledge you gained from the first app? Now imagine that you have 10 apps, are you going to go to the drawing board each time? It is a very compelling argument to use the existing skill set/tools the team has grown building their first containers-solution but that only holds true for the first few apps and only when containers are good solutions for these apps in the first place — it is only when you get to the ‘one thousand and one’ container when you realize you’re living in a containers orchestration/management nightmare.

I have seen this scenario multiple times with some variations with previous clients – the ability to containerize a legacy app and easily moving it to the cloud is indeed charming, few months into the project and you will wake up from the spell of containers only to find that Kubernetes is now the ruler of the Seven Kingdoms, what Kubernetes handles well goes well but areas that it doesn’t handle well will force you to come up with all kinds of ugly workarounds - Kubernetes limits now becomes your own limits. E.g. I had a client who spent some significant time containerizing an application, testing in the sandbox environment was promising, deploying to production was a centimeter away when we found out that the containers don’t hold their MAC addresses between restarts (i.e. Kubernetes assigns the MAC when the container starts and these addresses don’t persist), it is not a big deal for most apps but in this case, there was a licensing server that depends on MAC addresses for issuing licenses. The project was eventually dropped off before we were able to find a workaround. If you’re curious, the closest we came to resolving this was to use Azure Container Networking Interface (CNI) although it was still in the preview that time, it might be available by the time you’re reading this, however, this is just an example to demonstrate that containers limits can be hard to workaround.

My advice is to go back to the drawing board and cater to the hosting needs of each app. Consider breaking your app to microservices to build long-term maintainable solutions, use PaaS and Serverless whenever possible. There are many good articles online that discuss the use cases and benefits of using PaaS and Serverless, if there is one thing to mention here it would be the simple fact that the less time you spend on the lower layers (e.g. the webserver, the orchestration engine, the OS…etc.) the more time you get to focus on developing your own applications.

Simplicity is not discussed often enough

We (developers) love complexity – if you don’t believe me try it in your own dev team, ask developers to create ‘a fork and a knife’ application, give them few days, and watch them come back with a ‘swiss army knife’ application – Complex is often cool to implement and it might be exciting to add these amazing features that the requirements don’t call for…..until you deploy your solution and an issue appears, debugging an issue in a complex solution is hard and can waste a lot of valuable time.

Complexity is bad for too many obvious reasons, and it only gets worse over time – think of the 2nd law of thermodynamics, complex solutions will ever grow more complex unless you make Simplicity a primary objective and invest time and effort maintaining it throughout the lifecycle of the solution. We don’t discuss simplicity often enough and maybe we should start thinking of it as a requirement alongside the other non-functional requirements so we don’t compromise on it. In all cases, any effort of maintaining simplicity will require us to define what it is exactly that we mean by Simple.

(I was tempted to refer to ‘Simplicity’ as ‘Elegance’ because I feel simplicity is sometimes mistaken with being ‘basic’ or ‘incapable’. The solution requirements can be demanding yet still be met with simple/elegant solutions. If you tend to correlate simple solutions with incapable solutions please feel free to use the word ‘Elegant’ instead of ‘Simple’.)

What exactly is Simplicity?

You’re probably thinking that simplicity in its general sense is relevant and varies from one solution to another – I agree, let’s define it in a way that makes it measurable so we can decide if containers (and everything else) are good or bad in terms of simplicity.

In my view, a simple solution is a solution that every piece of it has a clear purpose that directly serves one or more of the solution requirements & nothing beyond them. The purpose (or in Aristotle’s terms the “final cause”) of each component (and each piece of code and each and everything else in the solution) must be clear and easily identifiable (intuitive).

For example, let’s say we have a requirement to have a web page where users can see their shopping history list, if you read this requirement you would intuitively expect some kind of ViewModel that reflects this list; the ‘final cause’ of this ViewModel is to be the shopping history list on that web page. Here is the thing, the opposite should also be true – even if you didn’t know the requirements, if you clone the repo and see this ViewModel it is intuitive for you to know its purpose, the minute you see it you will intuitively infer there must be a requirement for users to see their shopping history list. This ViewModel is therefore simple, it doesnt add complexity to the solution because it has that clear requirement-driven purpose attached to it.

Let’s apply this to containers, if you clone a solution written in C++, the next thing you would be looking for is a Docker file because abstracting away the dependencies is essential in making this solution portable – using containers here has an intuitive purpose that helps in keeping the solution simple. If on the other hand the solution is built on top of a modern/cross-platform technology stack, boxing it in a container is a layer of unintuitive and unjustifiable complexity. Convenience is the enemy of simplicity.


Containers have many use cases where they can bring a lot of power and simplicity to your solutions but they also can add a lot of complexity. Analyze the requirements first then decide on what technology stack is the most appropriate. Avoid the common fallacy of using containers just because you invested time and effort building your first containerized solution. Containers, like any other technology, have their limits and the abstraction layer they add is not free in terms of performance. If you can do without containers you should absolutely aim for that and resort to using containers only when there is a clear purpose for going this route.

About the Author

Alaa Tadmori is a Cloud Solutions Architect at Microsoft. He is an early adopter and enthusiast of the cloud computing model. Alaa believes in responsible IT, the solutions we’re building today are shaping our world and will be the world of our children so we have a responsibility and a choice everyday to make our world a better place. When not at work Alaa likes to spend time with his family and read nonfiction books.

Rate this Article