Intro
dockercompose Allows managing containers, networks, port-mappings, environment variables, run flags etc. Very useful for multi container systems. It defines every service, network, and volume in a yaml file named docker-compose.yaml.
Compose builds a custom network automatically for all the services with DNS resolution.
Docker Compose is separate from the Docker daemon but check if it's already installed since it's part of the toolbox
When’s It Needed?
Simply whenever your containerized app relies on multiple interconnected services that also run in their own containers. Example is a web server that needs a database, and maybe some memory-caching service like Redis.
Other Cases
- Development Environments: really simplifies local setup by starting the entire project stack with one command
- Automated Testing: It is ideal for creating and destroying isolated environments quickly for CI/CD pipelines or end-to-end testing.
- Version-Controlled Infrastructure: Since infrastructure’s defined in YAML file, changes can be tracked by VCS like and shared
Basic Commands
docker compose upto spin it all to lifedocker compose downto shut it
How It Works
As mentioned, Docker Compose relies on docker-compose.yaml to determine what to do in order to run the containers comprising the app.
It works in all environments, including CI/CD pipelines, but is more suited fro dev and testing.
Docker-Compose File
The YAML file has several parts and parameters. I’ll go through them by order and importance.
versionthis defines the Docker Compose version and is at the very top of the file.services: a nested paramer that defines the different services that each run in their own containervolumes: define persistent data storage volumes (shared) for your containers to maintain data
Here’s an example file from the Docker Curriculum:
version: "3"
services:
es:
image: docker.elastic.co/elasticsearch/elasticsearch:6.3.2
container_name: es
environment:
- discovery.type=single-node
ports:
- 9200:9200
volumes:
- esdata1:/usr/share/elasticsearch/data
web:
image: yourusername/foodtrucks-web
command: python3 app.py
depends_on:
- es
ports:
- 5000:5000
volumes:
- ./flask-app:/opt/flask-app
volumes:
esdata1:
driver: localNotice how the services are named.
Each service must have an image parameter to determine its container’s image…
Whereas the depends_on parameter for the web service, tells docker to start the es container before web.
Check out the online reference for all the parameters.
Environment variable from .env
If your variables are in .env you can add the following to a service’s config:
services:
web:
#...all other config fields
environment:
- DB_HOST=${DB_HOST}
- DB_USER=${DB_USER}
# Or inject the whole file:
env_file:
- .envReal Secrets
For secrets like DB passwords and TLS certificates, use the secrets field:
services:
db:
image: postgres
secrets:
- db_password
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
db_password:
file: ./dev_password.txt[!.tip] Many official Docker images support a
_FILEsuffix
Telling the application to read the secret from a mounted file rather than an environment variable. This is much more secure.
Architecture during Dev
As I integrated Docker Compose into my tool-set, I quickly realized I don’t know how to structure everything.
Sometimes I have multiple containerized applications, each with their own Dockerfile and Git repository. How does Docker Compose consolidate that?
Simply place the docker-compose.yaml in your project’s root directory /, wrapping all your different apps (repos)
In Prod
In production, Compose finds the Images via the
imagefield which points to a registry. See previous exampels.
Parent Mono-repo
A Mono-repo containing the YAML file in the root and the apps in their own sub-directories.
Inside that root docker-compose.yaml, tell Docker Compose exactly where to find local Dockerfiles using a relative path via the build:context field
Sibling Repos
Each app still lives in its own sub-directory with its own Git repo and Dockerfile. However, one of them also has the Compose file.
So for the context field, it’s . for the one containing the compose file, and the relative path for the other.
Health Checks
Like mentioned before some services rely on others and the depends_on parameter tells DC this. However, it’s not enough. A more robust way to check things are healthy is to conduct a health check.
This is a block within a service that tells DC how to do so.
Take a look at this example:
services:
web:
build: .
ports:
- "${APP_PORT}:5000"
environment:
- REDIS_HOST=${REDIS_HOST}
- REDIS_PORT=${REDIS_PORT}
depends_on:
redis:
condition: service_healthy
redis:
image: redis:alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
start_period: 10s- The
testparameter lists the commands to run - The
start_peroidis the delay before DC checks, gives Redis time to spin up
To verify, simply run it with docker compose up, then should see:
[+] Running 2/2
✔ Container compose-demo-redis-1 Healthy Project idea
Retrofit existing (AudioReaper is Flask and Redis) or deploy a new app with a docker-compose.yml that spins up the app and its dependencies in one command.
What to learn
docker-compose.ymlstructure: services, networks, volumes,depends_on, healthchecks- Environment variable injection via
.envfiles - Compose for local dev vs. production considerations