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:
docker-compose up
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:
version: '3'
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.
services:
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).
build: .
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 Dockerfile
. The build
key denotes where to find that file. In that Dockerfile
you’ll specify the base image you’ll use, say ubuntu
, redis
, etc. By default, it pulls images from here, 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
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 Dockerfile
. The build
key denotes where to find that file. In that Dockerfile
you’ll specify the base image you’ll use, say ubuntu
, redis
, etc. By default, it pulls images from here, 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.
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 http://localhost:8000
.
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.