Skip to content

About the author of Daydream Drift

Tomasz Niezgoda (LinkedIn/tomaszniezgoda & GitHub/tniezg) is the author of this blog. It contains original content written with care.

Please link back to this website when referencing any of the materials.

Author:

Replicate Host Commands In Docker Containers

Published

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.