Docker Compose — Multi-Container Apps
Docker Compose in CI/CD — Testing Multi-Service Apps Locally
Building testing environments and teardown scripts.
Interview: Interviewers love to ask how you optimize integration testing loops in pipelines. Know how to run Compose in non-interactive mode (`-d`), use the `--exit-code-from` flag to run test runners and propagate exit codes, and clean up test resources to prevent disk leaks.
Introduction
Unit testing mocks out remote database and API interfaces. However, to guarantee database schema safety or mock third-party dependencies accurately, you must execute Integration Tests against real service engines. Docker Compose is a powerful tool to spin up these environments deterministically.
Why It Matters
Manual pipeline testing leads to "flaky tests" due to dirty databases or old caches. Docker Compose lets you define a clean, reproducible testing environment that boots up empty, runs tests, and terminates cleanly on every single commit.
Real-World Analogy
Testing on a persistent server is like practicing chemistry experiments on the same lab table without cleaning it—previous chemical leaks (dirty databases) will contaminate the new experiments. Using Docker Compose in CI/CD is like having a robot set up a brand new, sterile test tube lab rack, run the experiment, read the result, and immediately throw the test tubes away, leaving the room perfectly clean.
How It Works
In CI/CD environments (like GitHub Actions, GitLab CI, or Jenkins), runners evaluate if a pipeline step succeeds or fails based on the exit code of the command. If you run a Compose stack, the command typically keeps running until you manually send a termination signal.
To solve this, use the --exit-code-from <service_name> flag. This tells Docker Compose to run the stack, execute the test runner container, monitor its exit code, and then automatically stop the entire stack, returning that service's exit code back to the shell environment.
Internal Architecture
When --exit-code-from triggers, the Compose controller halts execution threads and executes an internal docker compose down sequence. The return code from the target container is stored and passed back to the parent shell process.
Visual Explanation
Practical Example
Here is a practical integration testing configuration using Docker Compose inside a GitHub Actions pipeline:
Common Mistakes
1. Missing Volume Cleanup: Forgetting the -v flag when running docker compose down. This leaves old database files intact on the CI host, causing future runs to inherit old schema states.
2. Ignoring Teardown in Failure: If tests fail, the pipeline exits immediately. If your teardown script isn't flagged with if: always(), the containers remain running, locking port allocations.
Quick Quiz
Q1: Which flag guarantees that all containers stop immediately if any single container in the compose stack exits?
A) --exit-code-from
B) --abort-on-container-exit
C) -d
Answer: B — --abort-on-container-exit will monitor container states and shut down everything when one exits.
Scenario-Based Challenge
Scenario:
Your CI/CD pipeline runs tests in parallel. Two parallel runners spin up the same docker-compose testing file on the same host, causing port conflict errors. How do you isolate these parallel runs?
View Solution
Use the -p (or --project-name) flag to assign a unique project name suffix to each test runner run (e.g. using the build/job ID: docker compose -p project_run_${GITHUB_RUN_ID} up). This creates isolated network subnets and prefixes container names, avoiding conflicts. Also, avoid binding static ports to the host interface inside the test YAML files during parallel host operations.
Debugging Exercise
Failure Case:
The CI integration job passes successfully, but the pipeline log prints warning errors stating: Volume test_db_data is in use by orphaned container.
How would you debug this?
View Solution
The teardown script failed to execute because the build terminated prematurely or did not run docker compose down -v.
Fix: Ensure the teardown step includes the volume-delete flag (-v) and is configured to run even if previous steps fail.
Interview Questions
1. How do you capture the exit code of an integration test suite inside a Docker Compose run?
By running Compose with --exit-code-from <test-service-name>, which intercepts the test runner container's exit code and returns it directly to the host shell environment.
2. What does docker compose down -v do?
It tears down the containers and networks, and also deletes all persistent volume directories declared in the volumes section of the compose file.
Production Considerations
Verify your CI/CD runner disk space usage periodically. Running multiple builds with --build flags leaves unused image caches, which eventually fills up host runner storage disks. Run docker system prune -af as a scheduled cron task.