Quan desplegam aplicacions amb múltiples serveis, la comunicació entre ells és fonamental: l’aplicació ha de poder connectar-se a la base de dades i a la memòria cau, el proxy invers ha de poder connectar-se a l’aplicació i, potser, un servei d’administració necessita connectar-se a diversos backends.
Al mateix temps, sovint volem aïllar certs serveis perquè no siguin accessibles des d’altres parts de l’aplicació o des de l’exterior.
Docker Compose simplifica la gestió de xarxes creant automàticament una xarxa compartida per a tots els serveis d’un projecte i permetent-nos definir xarxes personalitzades quan necessitam més control.
Xarxa per defecte #
Quan executam docker compose up, Compose crea automàticament una xarxa per al projecte. Aquesta xarxa rep el nom <projecte>_default, on <projecte> és el nom del directori que conté el fitxer compose.yaml.
Tots els serveis definits al fitxer es connecten automàticament a aquesta xarxa i poden comunicar-se entre ells usant el nom del servei com a hostname. Per exemple, si tenim dos serveis webapp i db, des de webapp podem connectar-nos a la base de dades usant simplement db com a hostname.
services:
webapp:
image: myapp:latest
environment:
DATABASE_HOST: db # Resolució automàtica pel nom del servei
db:
image: postgres:18-alpineDocker incorpora un servidor DNS intern que resol automàticament els noms dels serveis a les seves adreces IP dins la xarxa. Aquesta resolució és dinàmica: si un contenidor es reinicia i obté una IP diferent, el DNS s’actualitza automàticament.
Per inspeccionar la xarxa per defecte i veure quins contenidors hi estan connectats usarem la següent comanda:
docker network inspect <projecte>_defaultLa sortida mostra informació detallada sobre la xarxa, incloent la subxarxa, la passarel·la i els contenidors connectats amb les seves adreces IP.
Xarxes personalitzades #
Tot i que la xarxa per defecte és suficient per a molts de casos, les xarxes personalitzades ens ofereixen avantatges importants:
- Aïllament: Podem separar grups de serveis que no necessiten comunicar-se entre ells.
- Seguretat: Els serveis de backend no han de ser accessibles des de la xarxa frontend.
- Organització: Una arquitectura de xarxes clara fa el sistema més fàcil d’entendre i mantenir.
Per definir xarxes personalitzades, usam la clau networks a dos nivells: al nivell superior per declarar les xarxes i dins cada servei per especificar a quines xarxes es connecta.
services:
nginx:
image: nginx:alpine
networks:
- frontend
webapp:
image: myapp:latest
networks:
- frontend
- backend
db:
image: postgres:18-alpine
networks:
- backend
networks:
frontend:
backend:En aquest exemple, nginx i webapp poden comunicar-se a través de la xarxa frontend, mentre que webapp i db es comuniquen a través de la xarxa backend. Però nginx no pot accedir directament a db perquè no comparteixen cap xarxa. Aquesta és la base del patró d’aïllament frontend/backend.
Quan definim xarxes personalitzades, Compose no crea la xarxa
defaultautomàticament. Cada servei ha d’especificar explícitament a quines xarxes es connecta.
Configuració avançada #
Sovint no ens cal fer res més que declarar les xarxes personalitzades que ens facin falta, però pot ser necessari definir una configuració avançada per a alguns casos especials.
Driver #
Docker suporta diferents drivers de xarxa. El driver per defecte és bridge, adequat per a la majoria de casos en un sol host:
networks:
frontend:
driver: bridgeAltres drivers disponibles inclouen host (comparteix la xarxa de l’amfitrió), overlay (per a clústers Docker Swarm) i none (sense xarxa).
Subxarxes #
Podem especificar la configuració IP de la xarxa amb la clau ipam (IP Address Management):
networks:
backend:
driver: bridge
ipam:
config:
- subnet: 172.28.0.0/16
gateway: 172.28.0.1Adreces estàtiques #
Per defecte, Docker assigna adreces IP dinàmicament. Si necessitam una IP fixa per a un servei, podem especificar-la:
services:
db:
image: postgres:18-alpine
networks:
backend:
ipv4_address: 172.28.0.10
networks:
backend:
ipam:
config:
- subnet: 172.28.0.0/16Assignar IPs estàtiques sol ser innecessari gràcies a la resolució DNS automàtica. Reserva aquesta opció per a casos especials.
Àlies de xarxa #
Un servei pot tenir múltiples noms dins una xarxa usant aliases:
services:
db:
image: postgres:18-alpine
networks:
backend:
aliases:
- database
- postgresAra, des de qualsevol servei connectat a backend, podem accedir a la base de dades com db, database o postgres.
Xarxes internes #
Podem crear xarxes sense accés a l’exterior amb la clau internal:
networks:
backend:
internal: trueEls serveis connectats a una xarxa interna poden comunicar-se entre ells, però no tenen accés a Internet ni poden ser accedits des de fora de Docker. Això és ideal per a bases de dades i altres serveis que no necessiten connectivitat externa.
Xarxes externes #
Podem connectar-nos a xarxes que existeixen fora del projecte Compose amb la clau external:
networks:
proxy:
external: trueAixò és útil quan tenim un proxy invers compartit entre múltiples projectes Compose. La xarxa ha d’existir prèviament, podem crear-la amb la següent comanda:
docker network create proxyExemple pràctic #
Posem en pràctica el que hem après amb un stack LEMP (Linux, NGINX, MariaDB, PHP). Crearem una arquitectura amb separació clara entre frontend i backend:
- NGINX servirà contingut estàtic i farà de proxy invers cap a PHP-FPM.
- PHP-FPM processarà els scripts PHP i es comunicarà amb MariaDB.
- MariaDB emmagatzemarà les dades i només serà accessible des del backend.
L’acrònim LEMP ve de Linux + Engine-X (NGINX) + MySQL/MariaDB + PHP. La “E” representa la pronunciació de NGINX (“Engine-X”). És equivalent al clàssic LAMP, substituint Apache per NGINX.
Estructura del projecte #
El nostre projecte tendra la següent estructura:
lemp/
├── compose.yaml
├── .env
├── docker/
│ ├── nginx/
│ │ └── default.conf
│ └── php/
│ └── Dockerfile
└── src/
└── index.phpComencem per crear el directori de feina i els subdirectoris necessaris:
mkdir --parents ~/Projects/lemp/docker/nginx ~/Projects/lemp/docker/php
mkdir --parents ~/Projects/lemp/src
cd ~/Projects/lempFitxer .env
#
El fitxer .env contendrà les variables d’entorn per a MariaDB:
# MariaDB
MARIADB_ROOT_PASSWORD=rootpassword
MARIADB_DATABASE=webapp
MARIADB_USER=webapp
MARIADB_PASSWORD=webapppasswordConfiguració d’NGINX #
El fitxer docker/nginx/default.conf configura NGINX per servir fitxers PHP a través de FastCGI. Per simplicitat, farem que només escolti al port 80:
server {
listen 80;
server_name localhost;
root /var/www/html;
index index.php index.html;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass php:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}Observa que fastcgi_pass php:9000 usa el nom del servei php com a hostname. Això funciona perquè ambdós serveis estaran connectats a la mateixa xarxa.
Dockerfile de PHP-FPM #
La imatge oficial de PHP no inclou les extensions de base de dades per defecte. Per tant, necessitam crear un fitxer docker/php/Dockerfile per configurar la imatge amb les extensions necessàries:
FROM php:8.4-fpm-alpine
RUN apk add --no-cache --virtual .build-deps $PHPIZE_DEPS \
&& docker-php-ext-install pdo_mysql mysqli \
&& apk del .build-deps
EXPOSE 9000Aquest Dockerfile:
- Parteix de la imatge oficial
php:8.4-fpm-alpine. - Instal·la temporalment les eines de compilació necessàries.
- Afegeix les extensions
pdo_mysqlimysqliper a la connexió amb MariaDB. - Elimina les eines de compilació per reduir la mida de la imatge.
Si inspeccionam la imatge:
docker run --rm php:8.4-fpm-alpine whoamiVeurem que el procés principal (php-fpm: master process) s’executa com a root, però els workers que processen les peticions s’executen com a usuari www-data (UID 82 a Alpine). Això és el comportament estàndard de PHP-FPM: el procés mestre necessita privilegis per gestionar els workers i obrir el port, però els workers que executen el codi PHP no són privilegiats.
Aplicació PHP #
El fitxer src/index.php comprova la connexió amb MariaDB i mostra la informació de PHP, suficient per a verificar el correcte funcionament:
<?php
echo "<h1>Stack LEMP amb Docker Compose</h1>";
echo "<h2>Connexió a MariaDB</h2>";
$host = 'db';
$user = getenv('MARIADB_USER');
$pass = getenv('MARIADB_PASSWORD');
$name = getenv('MARIADB_DATABASE');
try {
$pdo = new PDO("mysql:host=$host;dbname=$name", $user, $pass);
echo "<p style='color: green;'>Connexió exitosa a MariaDB!</p>";
echo "<p>Versió del servidor: " . $pdo->query('SELECT VERSION()')->fetchColumn() . "</p>";
} catch (PDOException $e) {
echo "<p style='color: red;'>Error de connexió: " . $e->getMessage() . "</p>";
}
echo "<h2>Informació de PHP</h2>";
phpinfo(INFO_GENERAL | INFO_MODULES);
?>
Fitxer compose.yaml
#
Configuram els tres serveis de la nostra aplicació, el volum de la base de dades i les dues xarxes a través del fitxer compose.yaml:
services:
nginx:
image: nginx:1.29-alpine
container_name: lemp-nginx
ports:
- "8080:80"
volumes:
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
- ./src:/var/www/html:ro
depends_on:
- php
networks:
- frontend
restart: unless-stopped
php:
build: ./docker/php
container_name: lemp-php
volumes:
- ./src:/var/www/html
environment:
MARIADB_USER: ${MARIADB_USER}
MARIADB_PASSWORD: ${MARIADB_PASSWORD}
MARIADB_DATABASE: ${MARIADB_DATABASE}
depends_on:
- db
networks:
- frontend
- backend
restart: unless-stopped
db:
image: mariadb:12.2
container_name: lemp-db
environment:
MARIADB_ROOT_PASSWORD: ${MARIADB_ROOT_PASSWORD}
MARIADB_DATABASE: ${MARIADB_DATABASE}
MARIADB_USER: ${MARIADB_USER}
MARIADB_PASSWORD: ${MARIADB_PASSWORD}
volumes:
- mariadb-data:/var/lib/mysql
networks:
- backend
restart: unless-stopped
networks:
frontend:
backend:
internal: true
volumes:
mariadb-data:Observa com:
- El servei
phpusabuild: ./docker/phpen comptes d’image:, indicant a Compose que ha de construir la imatge a partir del Dockerfile. - NGINX només està connectat a
frontendi exposa el port 8080. - PHP-FPM està connectat a ambdues xarxes: rep peticions de NGINX via
frontendi es connecta a MariaDB viabackend. - MariaDB només està a
backend, completament aïllat de la xarxa pública. - PHP-FPM i MariaDB no publiquen ports, car els tres serveis es comuniquen internament i només el servei NGINX es comunica amb el exterior.
Arrencada i verificació #
Una vegada creats tots els fitxers nec necessaris, arrencam els serveis:
docker compose up --detachCompose detectarà el build: al servei php i construirà automàticament la imatge abans d’arrencar els contenidors.
Accedim a http://localhost:8080/ per veure la pàgina PHP. Hauríem de veure el missatge de connexió exitosa a MariaDB i la informació de PHP amb les extensions pdo_mysql i mysqli llistades.
Per verificar l’aïllament de xarxes, podem intentar fer ping des de NGINX a la base de dades:
docker exec lemp-nginx ping -c 2 dbEl ping fallarà perquè db no és resoluble des de la xarxa frontend. En canvi, des de PHP-FPM funcionarà:
docker exec lemp-php ping -c 2 dbPer inspeccionar les xarxes creades usarem la següent comanda:
docker network ls | grep lempLa sortida mostrarà les dues xarxes:
7b4c1ecae244 lemp_backend bridge local
a46644844df8 lemp_frontend bridge localBones pràctiques #
Algunes recomanacions per gestionar xarxes de manera efectiva:
Arquitectura:
- Separa frontend i backend sempre que sigui possible.
- Connecta cada servei només a les xarxes que realment necessita.
- Usa xarxes internes per a serveis que no necessiten accés a Internet.
Nomenclatura:
- Usa noms descriptius per a les xarxes, com
frontend,backendomonitoring. - Evita noms genèrics, com
network1onet, o excessivament abreujats, comfeobe.
Seguretat:
- No exposis ports de bases de dades a l’amfitrió si no és estrictament necessari.
- Considera usar xarxes internes per a serveis sensibles.
- Revisa periòdicament quins serveis tenen accés a quines xarxes.
Exercicis #
Es proposen dos exercicis pràctics per facilitar l’aprenentatge progressiu.
Exercici 1 #
Afegir Adminer per a administració de MariaDB
En aquest exercici es proposa afegir Adminer, una eina d’administració de bases de dades, a l’stack LEMP. Adminer ha de poder accedir a MariaDB però no ha de ser accessible des de NGINX. Passos:
- Parteix de l’exemple LEMP de l’article.
- Afegeix un servei
admineramb la imatgeadminer:latestal port 8081. - Connecta Adminer només a la xarxa
backend. - Verifica que pots accedir a Adminer a
http://localhost:8081i connectar-te a MariaDB. - Verifica que NGINX no pot comunicar-se amb Adminer.
Consulta la documentació d’Adminer a Docker Hub per a més informació sobre la configuració.
Respostes
Afegeix el servei Adminer al compose.yaml:
services:
nginx:
image: nginx:1.29-alpine
container_name: lemp-nginx
ports:
- "8080:80"
volumes:
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
- ./src:/var/www/html:ro
depends_on:
- php
networks:
- frontend
restart: unless-stopped
php:
build: ./docker/php
container_name: lemp-php
volumes:
- ./src:/var/www/html
environment:
MARIADB_USER: ${MARIADB_USER}
MARIADB_PASSWORD: ${MARIADB_PASSWORD}
MARIADB_DATABASE: ${MARIADB_DATABASE}
depends_on:
- db
networks:
- frontend
- backend
restart: unless-stopped
db:
image: mariadb:12.2
container_name: lemp-db
environment:
MARIADB_ROOT_PASSWORD: ${MARIADB_ROOT_PASSWORD}
MARIADB_DATABASE: ${MARIADB_DATABASE}
MARIADB_USER: ${MARIADB_USER}
MARIADB_PASSWORD: ${MARIADB_PASSWORD}
volumes:
- mariadb-data:/var/lib/mysql
networks:
- backend
restart: unless-stopped
adminer:
image: adminer:latest
container_name: lemp-adminer
ports:
- "8081:8080"
networks:
- backend
restart: unless-stopped
networks:
frontend:
backend:
volumes:
mariadb-data:Executa Docker Compose:
docker compose up --detachAccedeix a http://localhost:8081 i connecta’t amb les dades següents:
- Sistema: MySQL
- Servidor: db
- Usuari: webapp
- Contrasenya: webapppassword
- Base de dades: webapp
Per verificar l’aïllament, intenta fer ping des de NGINX a Adminer:
docker exec lemp-nginx ping -c 2 adminerEl ping fallarà perquè no comparteixen xarxa.
Exercici 2 #
Afegir un comptador de visites amb Valkey
En aquest exercici es proposa afegir Valkey, un emmagatzematge clau-valor compatible amb Redis, a l’stack LEMP per implementar un comptador de visites. Passos:
- Parteix de l’exemple LEMP de l’article.
- Modifica el fitxer
docker/php/Dockerfileper afegir l’extensióredis(compatible amb Valkey). Consulta la documentació de la imatge oficial de PHP per veure com instal·lar extensions PECL. - Afegeix un servei
valkeyamb la imatgevalkey/valkey:9.0-alpinea la xarxabackend. - Crea el fitxer
src/comptador.phpamb el següent contingut:<?php echo "<h1>Comptador de visites</h1>"; $redis = new Redis(); try { $redis->connect('valkey', 6379); $visites = $redis->incr('comptador_visites'); echo "<p style='color: green;'>Connexió exitosa a Valkey!</p>"; echo "<p>Aquesta pàgina ha rebut <strong>$visites</strong> visites.</p>"; echo "<p><a href='comptador.php'>Recarrega</a> per incrementar el comptador.</p>"; } catch (RedisException $e) { echo "<p style='color: red;'>Error de connexió: " . $e->getMessage() . "</p>"; } ?> - Reconstrueix la imatge de PHP i reinicia els serveis.
- Accedeix a
http://localhost:8080/comptador.phpi verifica que el comptador s’incrementa amb cada visita. - Verifica que NGINX no pot comunicar-se amb Valkey.
Respostes
Modifica el fitxer docker/php/Dockerfile per afegir l’extensió redis:
FROM php:8.4-fpm-alpine
RUN apk add --no-cache --virtual .build-deps $PHPIZE_DEPS \
&& docker-php-ext-install pdo_mysql mysqli \
&& pecl install redis \
&& docker-php-ext-enable redis \
&& apk del .build-deps
EXPOSE 9000Afegeix el servei Valkey al compose.yaml:
services:
nginx:
image: nginx:1.29-alpine
container_name: lemp-nginx
ports:
- "8080:80"
volumes:
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
- ./src:/var/www/html:ro
depends_on:
- php
networks:
- frontend
restart: unless-stopped
php:
build: ./docker/php
container_name: lemp-php
volumes:
- ./src:/var/www/html
environment:
MARIADB_USER: ${MARIADB_USER}
MARIADB_PASSWORD: ${MARIADB_PASSWORD}
MARIADB_DATABASE: ${MARIADB_DATABASE}
depends_on:
- db
- valkey
networks:
- frontend
- backend
restart: unless-stopped
db:
image: mariadb:12.2
container_name: lemp-db
environment:
MARIADB_ROOT_PASSWORD: ${MARIADB_ROOT_PASSWORD}
MARIADB_DATABASE: ${MARIADB_DATABASE}
MARIADB_USER: ${MARIADB_USER}
MARIADB_PASSWORD: ${MARIADB_PASSWORD}
volumes:
- mariadb-data:/var/lib/mysql
networks:
- backend
restart: unless-stopped
valkey:
image: valkey/valkey:9.0-alpine
container_name: lemp-valkey
networks:
- backend
restart: unless-stopped
networks:
frontend:
backend:
internal: true
volumes:
mariadb-data:Crea el fitxer src/comptador.php amb el contingut proporcionat a l’enunciat.
Reconstrueix la imatge de PHP i reinicia els serveis:
docker compose up --detach --buildL’opció --build força la reconstrucció de les imatges, necessària perquè hem modificat el Dockerfile.
Accedeix a http://localhost:8080/comptador.php i refresca la pàgina diverses vegades per veure el comptador incrementar-se.
Per verificar l’aïllament, intenta fer ping des de NGINX a Valkey:
docker exec lemp-nginx ping -c 2 valkeyEl ping fallarà perquè no comparteixen xarxa.