We've all experienced it at one point or another. Something's broken in staging, or worse in production, but somehow it works on your machine. Same exact code, different results.
The answer to why this is happening is probably obvious — different builds, potentially different versions of things, and completely different environments.
Tracking down which one of these things is causing the issue is usually a little harder. Then, when you think you know what the culprit might be, reproducing that exact scenario locally to actually debug the problem can be difficult, time consuming, if not completely impossible.
Abstractly, Docker is an open platform for developing, shipping, and running applications. What that means concretely is that Docker provides a set of tools, APIs, and a registry that allows you to create, package, and run applications in a loosely isolated environment called a container.
If you're familiar with virtual machines, containers are similar but function slightly differently — a virtual machine virtualizes the host hardware, a container virtualizes the host operating system. This key distinction allows containers to be lightweight, portable, and efficient.
To create a container, you must first build a Docker Image by creating a
Dockerfile or using an existing
Dockerfile from the Docker registry. A
Dockerfile contains a set of commands similar to what a user would call on the command line that Docker will use to build an Image. By design, Images should be small and focused so that the parts of an application can be decoupled, ie. your proxy, your web application, your database. This strategy allows you to scale individual parts of your application and ultimately makes your application easier to debug.
Docker also provides an additional tool called Compose which allows you to create a network of multiple services — the parts of your application are also referred to as services in Compose. By defining a network with a
docker-compose.yml file, Compose makes it incredibly easy to link all your services together and coordinate creating, starting, and stopping them.
What all of this means in layman's terms is that Docker gives you the ability to consistently create and recreate any environment anywhere without having to install anything besides Docker. Have legacy App A on Node 8, but want to build App B to with Node 12 and run them on the same server, no problem. Want to ensure legacy App A can be upgraded to use the latest version of MongoDB, bump the Mongo version in your
Dockerfile and test it out. When your app is deployed, you can be confident that that it will function properly no matter what system your app is hosted on.
With Docker in your toolkit you can stop debugging your infrastructure, and instead focus on building and debugging your app.
This post really just scratches the surface of what Docker and Docker Compose can do. For more a more in depth look Docker and how you could use it with Apostrophe see Apostrophe + Docker Workshop.