Controlling How Docker Compose Exits

Joel Ramos
ramosly | blog
Published in
3 min readJun 22, 2020

--

Photo by Marcus Lenk on Unsplash

What follows will be a description of a small docker-compose challenge I had recently.

  • I needed two services in the compose file. The first service is long-running, the other runs and exits.
  • I wanted to run docker-compose up, spin up the long-running service, then run the second service which requires the first, then tear everything down.
  • I also wanted the run-and-exit service to build a new image each time when there were changes.

First Attempt

The point of this file was to run UI tests in a browser. Since getting all the requirements for running a browser in a container is painful, and since SeleniumHQ has already done the work of making node container images for this purpose, running this image separately made the most sense, especially since it would allow me to swap in a different image for a different browser later.

So, I created my docker-compose.yml file with one service for the browser image, and the other for my tests.

version: '3.7'
services:
selenium:
container_name: selenium
environment:
- CHROME_VERSION=83.0.0
- NO_PROXY=127.0.0.1,localhost,.domain.tld
- START_XVFB=false
healthcheck:
interval: 15s
retries: 3
timeout: 30s
test: "/opt/bin/check-grid.sh --host selenium --port 4444"
image: selenium/standalone-chrome:3.141.59-selenium
ports:
- "4444:4444"
logging:
driver: "none"
test:
container_name: test
build:
context: .
args:
- NPM_AUTH_TOKEN
env_file:
- .env
environment:
- SELENIUM_HOST=selenium
- SELENIUM_PATH=/wd/hub
- SELENIUM_PORT=4444
depends_on:
- selenium

Then I ran docker-compose up

Result

The test container image built from the Dockerfile in the local directory, the tests ran, and then the terminal just hung there after requiring me to CTRL-C out.

I realized that this was because the long-running browser container was just hanging out, waiting to receive more requests.

Second attempt

So I did some googling, and came across a couple command-line options for docker-compose that I was not aware of.

  • --abort-on-container-exit : tells docker-compose to exit when a container exits
  • --exit-code-from <service_name> : tells docker-compose from which container to use the exit code

Sweet!

So in my case, this turns my compose command into something like

docker-compose up \
--abort-on-container-exit \
--exit-code-from test

So now when I run this command:

  • The selenium container starts up first
  • The test container runs and exits
  • When the test container exits, the selenium container is stopped
  • docker compose exits 🎉

Note that “test” is the name of the service from the compose file, and not the container_name value from the compose file, which I happened to give the same name. … sorry 😬

One more thing…

docker-compose doesn’t build on each run.

What do I mean by that?

So, in my case, I have a Dockerfile in the root of the project, so instead of specifying an image for the test service, I had build: . instead thinking that each time I run docker-compose up it would build a new image before running.

I was wrong…

It turns out that using the build: . field in the docker-compose.yml file will cause the container to build the first time, but not subsequently, even after changes are made to the Dockerfile, or files in the repository.

In order to force the rebuild, you can run docker-compose build && docker-compose up

What I ended up with

After all that, I ended up with this:

docker-compose build && \
docker-compose up \
--abort-on-container-exit \
--exit-code-from test && \
docker-compose down

I tacked on docker-compose down at the end to tear down the default network and other stuff that compose creates to run the containers.

I’ll probably end up putting this in a shell script so that I don’t have to search my history to find this longer command each time I want to run it, or worse, type the whole thing out.

But, that’s it! This would be useful anytime you have a container running as a command that depends on another running container. I was glad to find that compose had solutions to my problem. Hopefully this helps someone else out there 🙂

--

--

• Economics grad from UC San Diego • QA Engineer • Pythonista • Always learning!