A common software development practice is to use development Docker containers locally. Doing so makes setting up projects for development easier, because the development environment is version controlled along with the source code of a project and shared among all developers.
As Docker and Docker Compose are complicated tools, there are many approaches to interacting with a project's source code through Docker containers.
Using a docker-compose.yml
file makes things more convenient. For one, a Compose file can setup things like a relational database and caching along with the main service which relies on them. Another advantage is the file describes the relationship to the host machine. So, port mappings, volume sharing, network settings can all be permanently configured inside the Compose file standardizing local setup even more.
One approach I found useful in this case, is to have a bin/exec
file in the project which relays commands to the project's main container. Here's an example of a bin/exec
file I used in multiple projects:
#!/bin/bash
PS=`docker-compose -f docker-compose.yml ps -q app`
if [ -z $PS ] || [ -z `docker ps -q --no-trunc | grep $PS` ]; then
echo "Not running, starting."
# Need to run Docker containers in the background, so it's shared between docker-compose exec commands.
# Otherwise, creating Docker containers every time a command is run (using `docker-compose run`) would
# require --service-ports to use the `ports:` config in docker-compose.yml` and collide with other
# `docker-compose run` commands. In short, this is the most predictable and easy to use approach.
docker-compose -f docker-compose.yml up -d
else
echo "Already running."
fi
docker-compose -f docker-compose.yml exec app bash -c "$*"
The file expects a app
service inside docker-compose.yml
but can be easily modified to search for a different "main" service and a different Docker Compose file instead of the default docker-compose.yml
(docker-compose-dev.yml
?).
The file has to be made executable by running chmod u+x bin/exec
.
When executed, it first starts all the Compomse services in the background and then executed the command provided as the argument. A crucial element is to prevent the main service from quitting immediately after bin/exec
starts it. To achieve this, set the command
value to tail -f /dev/null
. For example:
version: "3.0"
services:
app:
command: tail -f /dev/null
...
...
Afterwards, all that is left is to use bin/exec
. Here are some examples:
./bin/exec bundle install
./bin/exec LIVERELOAD=true MANGLE_JS=false npm run server
./bin/exec npm run build
The great thing about bin/exec
is that it makes running commands inside Docker containers very similar to running them directly on the host machine and hides some of the complexity of Docker. The above commands can be run without Docker like so:
bundle install
LIVERELOAD=true MANGLE_JS=false npm run server
npm run build
It gives the option for developers to choose how to run commands and the commands stay almost the same.
To ensure the project can be easily stopped after using bin/exec
, here's a complemenary bin/shutdown
file:
#!/bin/bash
docker-compose -f docker-compose.yml down -v
It will stop and remove containers, networks and volumes defined inside docker-compose.yml
. While rarely needed, the shutdown
file can be helpful when debugging issues with Docker or Docker Compose or changing the organisation of Docker services. Especially to people, who don't regularly work with Docker.