Docker Compose
Docker Compose is a program that simplifies the building of Docker applications by allowing you to define all the service components of the application in a single yaml file.
Unlike Docker Swarm or Kubernetes, it does not provide orchestration or any infrastructure level support around your application. However, it does simplify defining services that are intended to run on a single-node Docker node. Rather than starting Docker containers from the command line, where volumes and network linking can get complicated, the entire application can be laid out in a yaml file and the entire application can be deployed using the docker-compose
command.
Cheat Sheet
Commands
Task | Command |
---|---|
Start all services defined in a docker-compose.yml file. | docker-compose up -d
|
Stop all services
including volumes |
docker-compose down docker-compose down -v
|
Restart all services | docker-compose restart
|
Update containers and restart only changed images | docker-compose pull && docker-compose up -d
|
View logs | docker-compose logs
|
List containers that are deployed | docker-compose ps
|
View resource usage of running containers | docker-compose top
|
Validate docker compose file | docker-compose config
|
Service definitions
What | How |
---|---|
New service | version: '3.3'
services:
service_one:
...
|
Specify image | version: '3.3'
services:
service_one:
image: my-image:latest
|
Build the image | version: '3.3'
services:
service_one:
build:
context: ./build-directory
dockerfile: Dockerfile
|
Restart policy | version: '3.3'
services:
service_one:
restart: always
|
Specify uid/gid | version: '3.3'
services:
service_one:
user: "1000:1000"
Container runs as a specific uid/gid. Useful for enforcing rootless containers. |
Expose port | version: '3.3'
services:
service_one:
expose:
- "80"
- "443"
Exposes ports visible to other containers in the stack. |
Publish a port | version: '3.3'
services:
service_one:
ports:
- "80:8888"
- "192.168.1.1:80:88888"
Publishes a port on the docker host |
Add capabilities | version: '3.3'
services:
service_one:
cap_add:
- SYS_ADMIN
- NET_ADMIN
Add capabilities to the container. Refer to the Linux source for other keys. Common ones include:
|
NFS Volume
netshare plugin |
services:
service_one:
volumes:
- NetworkShare:/share
volumes:
NetworkShare:
driver: nfs
driver_opts:
share: "nas:/network/share"
You need the docker-volume-netshare plugin installed for this to work. |
NFS Volume
local driver only |
services:
service_one:
volumes:
- NetworkShare:/share
volumes:
NetworkShare:
driver: local
driver_opts:
type: nfs
o: "username=user,password=secret,addr=1.2.3.4,soft,rw"
# Share path on your NAS:
device: ":/share"
|
Samba / CIFS
Volume |
services:
service_one:
volumes:
- NetworkShare:/share
volumes:
NetworkShare:
driver: local
driver_opts:
type: cifs
o: "username=user,password=secret,rw"
device: "//nas/share"
|
Installation
To use Docker Compose, download the latest binary and stick it in your PATH. Get the binary from:
Example usage
For instance, if a LAMP stack is required, a docker-compose configuration file would define each of the components making up the LAMP stack (Apache+PHP, MySQL, optionally a Load Balancer). Rather than manually creating/removing each of the Docker containers and networks manually, the entire service can be brought up or down with one command.
An example docker-compose.yml
file containing such a LAMP stack:
version: '3.3'
services:
traefik:
image: traefik:latest
restart: always
command: --web --docker --docker.watch --docker.exposedbydefault=false
volumes:
- /var/volumes/lamp/traefik/traefik.toml:/traefik.toml
- /var/volumes/lamp/traefik/acme.json:/acme.json
- /var/run/docker.sock:/var/run/docker.sock:ro
networks:
external_net:
ipv4_address: 1.1.1.1
backend:
web:
build: web/.
restart: always
volumes:
- /var/volumes/lamp/data:/export/data
- /var/volumes/lamp/config:/export/config
networks:
backend:
ports:
- "8080:8080"
depends_on:
- db
labels:
- "traefik.enable=true"
- "traefik.port=8080"
- "traefik.frontend.rule=Host:hello.steamr.com"
db:
image: mariadb:10
restart: always
volumes:
- /var/volumes/lamp/db:/var/lib/mysql
environment:
- MYSQL_ROOT_PASSWORD=xxxx
- MYSQL_DATABASE=web
- MYSQL_USER=web
- MYSQL_PASSWORD=xxxx
networks:
- backend
aliases:
- mysql
networks:
external_net:
external: true
backend:
ipam:
config:
- subnet: 192.168.246.0/24
To bring this LAMP service up, run:
# ls
docker-compose.yml
## the '-d' is for detached mode
# docker-compose up -d
docker-compose
will then read the configuration file and set up the Docker networks and bring up the Docker containers with the required configs (image, volumes, network settings, environment variables, etc.) as defined in the file. Networking between different containers within the same service are done using dynamic linking automatically (rather than static links with hosts files) since individual containers can be recreated without bringing the entire stack down.
The stack can be brought down using
# docker-compose down
If any changes are made to the docker-compose.yml
file later on, the stack can be brought up to date by running docker-compose up
again:
# docker-compose up -d
Networking
Some notes on networking in docker-compose:
Ports
Expose ports. Either specify both ports (HOST:CONTAINER), or just the container port (a random host port will be chosen).
- Ports listed in
docker-compose.yml
will be accessible among all containers within this service. - These ports will be exposed to the host machine to a random port or a given port.
For example, we can start a MySQL server on port 3306. Any other container in this stack will be able to reach mysql:3306 (container-name:port). The DNS name is resolved by Docker's internal DNS service. The host machine is also able to reach the container, though its port will be randomized.
mysql:
image: mysql:5.7
ports:
- "3306"
If you need MySQL exposed to the host on a specific port, such as 3306, pass in the HOST:CONTAINER port numbers. Since this binds to a specific port, be mindful of any existing ports in use either as containers or services running on the host.
mysql:
image: mysql:5.7
ports:
- "3306:3306"
If the host machine has no firewall restricting access to port 3306, other machines that can reach the host machine will be able to talk on port 3306 to reach the MySQL service. If your host machine has multiple addresses, you may also bind to a specific address:
mysql:
image: mysql:5.7
ports:
- "10.1.2.3:3306:3306"
Expose
Exposed ports will only be accessible to other containers within this stack. Like above, other containers can reach the MySQL server at mysql:3306 (container-name:port). Since this port is not exposed and bound to the host machine's network address, you may have multiple containers exposing the same ports without running into conflicts.
mysql:
image: mysql:5.7
expose:
- "3306"
Networks
A service may connect to one or more networks. For example:
services:
nodered:
image: nodered/node-red-docker:latest
labels:
- traefik.enable=true
- traefik.docker.network=traefik
networks:
- traefik
- mqtt-net
expose:
- "1880"
restart: always
broker:
image: eclipse-mosquitto:1.6
networks:
- mqtt-net
expose:
- "1883"
ports:
- "10.1.2.200:1883:1883/tcp"
restart: always
networks:
mqtt-net:
traefik:
external:
name: traefik
Each of the networks listed under nodered and broker must be defined in the network section in the docker-compose.yml
file. Typically, you would want to associate a network with an actual docker network so that there is connectivity between other container stacks. In the example above, the mqtt-net network is private to only this container stack (and we don't really care what IPs they get, so long as they see each other) while the traefik network is connected to some external docker network which traefik (in another container stack) is connected to.
For more complex use cases, networks can be created with a different driver such as macvlan which allows for more advanced networking. For example, the following network configs were used for my Pi-Hole setup so that the container has access to a specific IP address on the network in order to allow DHCP to work.
pihole:
image: pihole/pihole
restart: unless-stopped
environment:
- "TZ=MST7MDT,M3.2.0,M11.1.0"
- ServerIP=10.1.0.8
volumes:
- '/var/volumes/pihole/etc-pihole/:/etc/pihole/'
- '/var/volumes/pihole/etc-dnsmasq.d/:/etc/dnsmasq.d/'
ports:
- "10.1.0.8:53:53/tcp"
- "10.1.0.8:53:53/udp"
- "10.1.0.8:67:67/udp"
- "10.1.0.8:80:80/tcp"
cap_add:
- NET_ADMIN
#network_mode: "host"
networks:
pihole_network:
ipv4_address: 10.1.0.8
networks:
pihole_network:
driver: macvlan
driver_opts:
parent: eth0
ipam:
config:
- subnet: "10.1.0.0/22"
gateway: "10.1.1.1"
ip-range: "10.1.0.8/32"