Als developer loop je wel eens tegen complexe projecten aan die onderling weer dependencies met elkaar hebben, hoe zorg je er nou voor dat dit gestroomlijnd lokaal gaat werken? Een antwoord hierop is Docker Compose en in dit blog vertel ik je daar wat meer over.
Het krachtige concept van micro services neemt al een aantal jaar langzaam aan de industrie over. Grote monolitische services maken langzaam plaats voor zwermen van kleine autonome microservices die samenwerken. Het proces gaat gepaard met een andere markttrend: Containers. Samen helpen ze ons veerkrachtige systemen te bouwen.
Containers veranderen niet alleen de architectuur van systemen, maar ook de structuur van omgevingen die worden gebruikt om ze te creëren. Wanneer software nu in containers wordt gedistribueerd, hebben ontwikkelaars de volledige vrijheid om te beslissen welke applicaties ze nodig hebben. Als resultaat kunnen complexe omgevingen zoals een React app met een API en WebSockets binnen enkele seconden worden geïnstantieerd. Softwareontwikkeling wordt eenvoudiger en effectiever.
Deze veranderingen veroorzaken natuurlijk ook een nieuw probleem. Hoe kan ik als ontwikkelaar er voor zorgen dat mijn ontwikkelomgeving gelijk is aan de uiteindelijke productie omgeving?
Docker Compose introductie
Het antwoord op deze vraag is Docker Compose:
“Een tool voor het definiëren en uitvoeren van complexe applicaties met Docker. Met Compose definieer je een applicatie met meerdere containers in één enkel bestand, en start je jouw applicatie vervolgens op met één enkel commando die alles doet wat nodig is om het te laten draaien.” https://docs.docker.com/compose/
In dit blog gaat we kijken hoe we Docker Compose kunnen gebruiken om een lokale ontwikkelomgeving op te zetten.
Functionaliteit
De hoofdzakelijke functie van Docker Compose is het creëren van een microservice architectuur, dus de containers en de onderlinge relaties. Maar deze tool kan nog veel meer.
Alle functionaliteit van Docker Compose is beschikbaar via het docker-compose commando, welke lijkt op de functionaliteit van Docker:
build Build or rebuild services
bundle Generate a Docker bundle from the Compose file
config Validate and view the Compose file
create Create services
down Stop and remove containers, networks, images, and volumes
events Receive real time events from containers
exec Execute a command in a running container
help Get help on a command
images List images
kill Kill containers
logs View output from containers
pause Pause services
port Print the public port for a port binding
ps List containers
pull Pull service images
push Push service images
restart Restart services
rm Remove stopped containers
run Run a one-off command
scale Set number of containers for a service
start Start services
stop Stop services
top Display the running processes
unpause Unpause services
up Create and start containers
version Show the Docker-Compose version information
Ze lijken niet alleen op elkaar, maar gedragen zich als Docker tegenhangers. Het enige verschil is dat zij effect hebben op het gehele multi-container architectuur die gedefinieerd staat in het docker-compose.yml configuratie bestand en niet een enkele container.
Je zult misschien merken dat niet alle Docker commando’s in docker-compose aanwezig zijn, deze zijn namelijk niet logisch voor een multi-container setup.
Docker Compose workflow
Er zijn drie stappen om Docker Compose te gebruiken:
1. Definieer elke service in een Dockerfile.
2. Definieer de services en de relaties tot elkaar in het docker-compose.yml bestand.
3. Gebruik docker-compose up om het systeem te starten.
Ik ga de workflow laten zien aan de hand van twee voorbeelden. In het eerste voorbeeld laat ik de basis structuur zien. En in het tweede voorbeeld gaan we ervoor zorgen dat die daadwerkelijk gedraaid kan worden.
Basis structuur
Als eerst gaan we een basis mappen structuur opzetten hoe ik het meestal doe voor complexere projecten, echter zijn er meerdere wegen die naar Rome leiden en dus kan je dit ook op vele andere manieren doen.
Project-map
├── apps > In deze map komen alle sub-projecten
├── containers > In deze map komen alle custom containers
├── data > In deze map komt bv. De mysql db te staan
├── logs > In deze map komen de logs van bv. nginx
└── docker-compose.yml
Nu we de basis mappen structuur hebben kunnen we de subprojecten toevoegen, denk hierbij aan een API, OAuth, front-end enz. Als voorbeeld hebben een Laravel API en een React front-end met daartussen een WebSocket communicatie laag (Laravel Echo Server). In werkelijkheid zijn dit 9 van de 10 keer Git Submodules aangezien je dit qua versie beheer apart wil houden, maar hier gaan we voor nu niet op in.
Project-map
├── apps
│ ├── laravel-api
│ ├── lavavel-echo-server
│ └── react
├── containers
├── data
├── logs
└── docker-compose.yml
Wanneer we dit hebben staan kunnen we er over nadenken welke containers de sub-projecten nodig hebben.
Laravel API
• Nginx
• PHP-FPM
• Redis (voor de communicatie naar Laravel Echo Server en als queue, session en cache driver)
• MySQL
Laravel Echo Server
• Node
• Redis
React front-end (dit is voornamelijk om productie builds te testen, niet voor development)
• Nginx
Als eerst gaan we in de containers map wat bestanden en mappen aanmaken voor containers die wat extra configuratie nodig hebben. Op de configuratie van specifieke containers gaan we nu niet in, aangezien dit op zichzelf al een blog verdient. Maar niet getreurd ik heb hier alle containers al geconfigureerd.
Project-map
├── apps
│ ├── laravel-api
│ ├── lavavel-echo-server
│ └── react
├── containers
│ ├── mysql
│ │ ├── Dockerfile
│ │ ├── docker-mysql-init.sql
│ │ └── my.cnf
│ ├── nginx
│ │ ├── Dockerfile
│ │ ├── nginx.conf
│ │ └── sites
│ │ ├── api.conf
│ │ └── app.conf
│ └── php-fpm
│ ├── Dockerfile
│ └── debug.ini
├── data
├── logs
└── docker-compose.yml
Docker Compose
Het is nu eindelijk tijd dat we de docker-compose.yml file gaan configureren, in deze configuratie kunnen we per container aangeven wat deze container nodig heeft om te draaien.
version: "3.1"
services:
### MySQL Container #########################################
mysql:
container_name: mysql
build:
context: ./containers/mysql
args:
- MYSQL_VERSION=5.7
environment:
- MYSQL_DATABASE=app_db
- MYSQL_USER=app_user
- MYSQL_PASSWORD=secret
- MYSQL_ROOT_PASSWORD=secret
volumes:
- ./data/mysql:/var/lib/mysql:cached
- ./containers/mysql/docker-mysql-init.sql:/docker-entrypoint-initdb.d/dump.sql:cached
ports:
- 3306:3306
networks:
- backend
### Redis Container #########################################
redis:
image: redis
container_name: redis
volumes:
- ./data/redis:/var/lib/redis:cached
ports:
- 6379:6379
environment:
- ALLOW_EMPTY_PASSWORD=yes
networks:
- backend
### PHP-FPM Container #######################################
php-fpm:
container_name: php-fpm
build:
context: ./containers/php-fpm
args:
- WITH_XDEBUG=true
volumes:
- ./apps/:/var/www:cached
- ./containers/php-fpm/xdebug.ini:/usr/local/etc/php/conf.d/xdebug.ini:cached
expose:
- 9000
depends_on:
- mysql
- redis
environment:
- PHP_IDE_CONFIG="serverName=app"
networks:
- backend
### NGINX Server Container ##################################
nginx:
container_name: nginx
build:
context: ./containers/nginx
args:
- PHP_UPSTREAM_CONTAINER=php_fpm
- PHP_UPSTREAM_PORT=9000
volumes:
- ./apps/:/var/www:cached
- ./logs/nginx/:/var/log/nginx:cached
- ./containers/nginx/sites/:/etc/nginx/sites-available:cached
ports:
- 80:80
- 443:443
depends_on:
- php-fpm
networks:
- frontend
- backend
### node Container ########################################
node:
image: node:alpine
container_name: node
volumes:
- ./apps/laravel-echo-server:/app:cached
working_dir: /app
ports:
- 4000:4000
restart: always
command: sh -c "npm start"
networks:
- frontend
- backend
### Networks Setup ##########################################
networks:
frontend:
driver: "bridge"
backend:
driver: "bridge"
Het enige wat we nu nog zouden moeten doen is docker-compose up draaien. Natuurlijk zal dit nu niks doen aangezien er nog geen applicatie is, maar aan de hand van dit voorbeeld zou je zonder veel problemen nu wel je eigen configuratie moeten kunnen maken. Dus ik daag jullie bij deze uit om een bestaande applicatie die je nu met bijvoorbeeld met MAMP draait, nu met Docker Compose te gaan draaien. Succes!!
Mocht je vragen hebben of kom je er niet uit? Mail me dan even!