Foundational Overview of the Docker Compose file
No more "Well, it works on my machine" using Docker Compose
Keeping a team working efficiently can be a challenge when you have an application that has many dependencies and requires specific configuration in order to run correctly. You can install each dependency on your machine, but forgetting one step could give you behavior that's different from everyone else. You could create VM with something like Vagrant and that can be possible, but that could put a lot of strain on your computer's resources and adding a new service is no simple matter. Or you could have a system where running a handful of commands (as little as one) can get you a server up and running, with the right dependencies, and configuration mostly hands free. That's what Docker Compose can do for you.
Docker Compose is essentially an abstraction on top of Docker where you could orchestrate the building and configuration of one or more containers in just one file. This file is named
docker-compose.yml. Here's an example from the Docker documentation:
version: '3' services: db: image: postgres web: build: . command: python manage.py runserver 0.0.0.0:8000 volumes: - .:/code ports: - "8000:8000" depends_on: - db
We'll break down the
docker-compose.yml file soon, but before that I wanted to note something. All you need is that one file and then running the following command and you'll have a complete application running with it's dependencies:
Yes, that's it. Just that one command. Docker Compose will do the rest for you. In the example above, what you'll have in the end is a Django app running on port 8000 with postgres configured (assuming you include the missing
Dockerfile stated in the Docker documentation above).
Examining the docker-compose.yml file
Now lets break down the
docker-compose.yml file. It's fairly straightforward once you understand it.
This denotes the version of docker-compose dialect that you'll use in the yaml configuration file. Over time, Docker adds to the dialect so specifying the version allows Docker to know if it can or can't run the directives inside of the yaml file.
Here you list your containers. It's best practice to have one container per service (.i.e. mysql by itself, your app by itself, redis by itself, etc). That allows multiple services to interact with each other and you could scale each container by itself. Occasionally there are cases where you'd put more than one service in a container, but it's highly encouraged to keep it to one service per container.
db: image: postgres
The first key
db is the name you want to put for your service. Docker Compose exposes all of the containers to each other so you could reference by their host name and their hostname will be the name you call the service in the compose file. The
image key is the base image your container will be based off of. Basically an image is a cookie cutter and a container is the baked cookie. You only need one cookie cutter (image) to make many of the same cookies (containers based off of one image).
If you need to build your container in a very specific way and add your own dependencies you have the flexibility of doing that using a
build key denotes where to find that file. In that
Dockerfile you'll specify the base image you'll use, say
redis, etc. By default, it pulls images from https://hub.docker.com/, but you could specify your own registry. Say, for instance, you need to install very specific libraries, maybe even compile a program manually, you would use a
Dockerfile to delineate all of those steps.
command: python manage.py runserver 0.0.0.0:8000
This is the command that's run after the container has been built and is running without any errors. You could specify any command that will run inside of the container. It's important that the command runs in the foreground or else your container will stop immediately. You could even specify any command that doesn't exit immediately like
tail -f /dev/null just to keep the container running.
volumes: - .:/code
This says to mount the local path
. to the path
/code inside of the container.
ports: - "8000:8000"
This means to forward calls from port
8000 on the host machine to port
8000 in the container. For instance, you could call the web service on your host machine with
depends_on: - db
If you need other services that you depend on like a database you can use
depends_on. This will bring up those containers in the listed order, but not specifically wait for them. There's also the similar
links key which waits until the containers listed under
links are actually running before it runs.
And there you go, that's an overview of how easy it is to use Docker Compose to set up your application very easily for you and for others in a repeatable and predictable fashion.