Salta el contingut

Pas 9 - Docker Compose

Al Pas 7 vam aixecar WordPress amb MySQL amb una xarxa, dos volums i dues comandes llargues. Funcionava, però era difícil de recordar, de compartir i de mantenir. Docker Compose permet descriure tota aquesta configuració en un fitxer YAML i gestionar l'aplicació sencera amb una sola comanda.

Objectius del pas

  • Traduir comandes docker run a un fitxer compose.yaml.
  • Gestionar el cicle de vida d'una aplicació sencera: up, ps, logs, down.
  • Combinar imatges de Docker Hub amb imatges pròpies construïdes amb build.
  • Controlar l'ordre d'arrencada amb depends_on i healthcheck, i separar la configuració amb un fitxer .env.

docker compose o docker-compose?

La versió actual és Compose v2, integrada a Docker com a subcomanda: docker compose (amb espai). L'antiga eina docker-compose (amb guionet) està obsoleta. El fitxer es pot dir compose.yaml (nom recomanat) o docker-compose.yml, i ja no cal la línia version: al principi.

9.1 De comandes a fitxer

Creeu un directori ~/docker-pas9/wordpress i, dins, un fitxer compose.yaml:

services:
  wp-bd:
    image: mysql:8.4
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: RootSeguret
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wp
      MYSQL_PASSWORD: WpSeguret
    volumes:
      - dades-bd:/var/lib/mysql

  wordpress:
    image: wordpress:6.7
    restart: unless-stopped
    ports:
      - "8000:80"
    environment:
      WORDPRESS_DB_HOST: wp-bd:3306
      WORDPRESS_DB_USER: wp
      WORDPRESS_DB_PASSWORD: WpSeguret
      WORDPRESS_DB_NAME: wordpress
    volumes:
      - fitxers-wp:/var/www/html
    depends_on:
      - wp-bd

volumes:
  dades-bd:
  fitxers-wp:

Compareu-lo amb les comandes del pas 7. Cada opció de docker run té la seva clau al fitxer:

docker run compose.yaml
--name wp-bd el nom del servei (wp-bd:)
la imatge al final image:
-p 8000:80 ports:
-e VAR=valor environment:
--env-file fitxer env_file:
-v volum:/ruta volumes: (i declarar el volum a volumes: de nivell superior)
--network xarxa networks: (Compose en crea una per defecte)
--restart unless-stopped restart:
docker network create no cal: Compose crea una xarxa per a l'aplicació

La xarxa ha desaparegut del fitxer: Compose crea automàticament una xarxa per a cada projecte i hi connecta tots els serveis, que es troben entre ells pel nom del servei. Per això WORDPRESS_DB_HOST continua sent wp-bd.

9.2 Aixecar, observar i aturar

docker compose up -d
[+] Running 5/5
 ✔ Network wordpress_default       Created
 ✔ Volume "wordpress_dades-bd"     Created
 ✔ Volume "wordpress_fitxers-wp"   Created
 ✔ Container wordpress-wp-bd-1     Started
 ✔ Container wordpress-wordpress-1 Started

Si les imatges no eren locals, abans veureu com les descarrega: exactament el mateix que feia docker run al pas 1. Fixeu-vos en els noms: Compose posa com a prefix el nom del projecte, que per defecte és el nom del directori (wordpress). Així, dos projectes diferents poden tenir un servei wp-bd sense conflicte.

Tot el que hi ha a sota són contenidors, xarxes i volums normals: docker ps, docker network ls i docker volume ls els mostren. Compose només ens estalvia escriure les comandes.

docker compose ps                  # serveis d'aquest projecte
docker compose logs -f wordpress   # logs d'un servei (sense nom: de tots)
docker compose exec wp-bd mysql -uwp -pWpSeguret wordpress
docker compose stop                # atura els contenidors sense eliminar-los
docker compose start
docker compose down                # atura i ELIMINA contenidors i xarxa (els volums es mantenen)
docker compose down -v             # també elimina els volums: es perden les dades
flowchart LR
    F[compose.yaml] -->|docker compose up| P
    subgraph P[Projecte wordpress]
        N[Xarxa wordpress_default]
        S1[Contenidor wp-bd]
        S2[Contenidor wordpress]
        V1[(Volum dades-bd)]
        V2[(Volum fitxers-wp)]
    end
    classDef a fill:#7C3AED,stroke:#5B21B6,color:#FFFFFF
    classDef b fill:#2563EB,stroke:#1E40AF,color:#FFFFFF
    classDef c fill:#16A34A,stroke:#166534,color:#FFFFFF
    class F a
    class N,S1,S2 b
    class V1,V2 c

El fitxer és la documentació

Ara, per donar l'entorn a un company només cal passar-li el compose.yaml (normalment dins d'un repositori Git). Amb docker compose up -d tindrà exactament el mateix que vosaltres. Compareu-ho amb el fitxer de comandes que vau escriure a la miniactivitat del pas 7.

9.3 Canviar la configuració

Editeu el fitxer, per exemple canviant el port a "8080:80", i torneu a executar:

docker compose up -d

Compose compara el fitxer amb el que hi ha en marxa i només recrea els contenidors que han canviat (aquí, wordpress). Les dades no es perden, perquè són als volums.

9.4 Imatges pròpies: build

Compose també pot construir imatges a partir d'un Dockerfile. Reprenem la idea del pas 8 amb una aplicació Flask que desa comentaris en una base de dades PostgreSQL.

app-comentaris/
├── compose.yaml
├── .env
├── init.sql
└── web/
    ├── Dockerfile
    ├── app.py
    └── requirements.txt

web/app.py:

from flask import Flask, request, render_template_string
import psycopg2
import os

app = Flask(__name__)

def connexio():
    return psycopg2.connect(
        host=os.environ['DB_HOST'],
        dbname=os.environ['DB_NAME'],
        user=os.environ['DB_USER'],
        password=os.environ['DB_PASSWORD'],
    )

PLANTILLA = '''
<h1>Comentaris</h1>
<form method="post">
  <textarea name="comentari" rows="3" cols="50"></textarea><br>
  <input type="submit" value="Afegir comentari">
</form>
<ul>{% for c in comentaris %}<li>{{ c[0] }}</li>{% endfor %}</ul>
'''

@app.route('/', methods=['GET', 'POST'])
def index():
    with connexio() as conn, conn.cursor() as cur:
        if request.method == 'POST':
            cur.execute('INSERT INTO comentaris (text) VALUES (%s)', (request.form['comentari'],))
        cur.execute('SELECT text FROM comentaris ORDER BY id DESC')
        comentaris = cur.fetchall()
    return render_template_string(PLANTILLA, comentaris=comentaris)

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

web/requirements.txt:

Flask==3.1.0
psycopg2-binary==2.9.10

web/Dockerfile (el mateix patró del pas 8):

FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 5000
CMD ["python", "app.py"]

init.sql:

CREATE TABLE IF NOT EXISTS comentaris (
    id SERIAL PRIMARY KEY,
    text TEXT NOT NULL,
    creat_a TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

.env (Compose el llegeix automàticament i en substitueix les variables ${...} del fitxer):

DB_NAME=comentaris
DB_USER=usuari
DB_PASSWORD=contrasenya_segura
WEB_PORT=5000

compose.yaml:

services:
  web:
    build: ./web
    image: app-comentaris:1.0
    ports:
      - "${WEB_PORT}:5000"
    environment:
      DB_HOST: db
      DB_NAME: ${DB_NAME}
      DB_USER: ${DB_USER}
      DB_PASSWORD: ${DB_PASSWORD}
    depends_on:
      db:
        condition: service_healthy

  db:
    image: postgres:17-alpine
    environment:
      POSTGRES_DB: ${DB_NAME}
      POSTGRES_USER: ${DB_USER}
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - dades-postgres:/var/lib/postgresql/data
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${DB_USER} -d ${DB_NAME}"]
      interval: 5s
      timeout: 3s
      retries: 10

volumes:
  dades-postgres:

Aquí apareixen quatre idees noves:

  • build: ./web: en lloc de descarregar una imatge, Compose la construeix amb el Dockerfile d'aquest directori. Amb image: li posem nom i etiqueta.
  • Bind mount i volum alhora: init.sql es munta al directori /docker-entrypoint-initdb.d/, on la imatge de PostgreSQL executa els scripts en crear la base de dades per primer cop. Les dades van a un volum amb nom.
  • healthcheck: una ordre que Docker executa periòdicament per saber si el servei està realment llest, no només arrencat.
  • depends_on amb condition: service_healthy: web no arrenca fins que la base de dades respon. Sense aquesta condició, depends_on només garanteix l'ordre d'arrencada, i Flask podria intentar connectar-se abans que PostgreSQL acceptés connexions.
docker compose up -d --build
docker compose ps          # db apareix com a "healthy"

Obriu http://localhost:5000 i afegiu comentaris. Proveu:

  • docker compose down i docker compose up -d: els comentaris continuen (volum).
  • Modifiqueu app.py i feu docker compose up -d --build: només es reconstrueix i es recrea web, aprofitant la memòria cau de capes.

El fitxer .env no s'ha de pujar a Git

Conté contrasenyes. Afegiu-lo al .gitignore i pugeu en canvi un .env.example amb els noms de les variables i valors d'exemple.

9.5 Comandes de Compose

Comanda Què fa
docker compose up -d Crea i arrenca tot el projecte en segon pla
docker compose up -d --build Igual, reconstruint les imatges amb build
docker compose ps Estat dels serveis
docker compose logs [-f] [servei] Logs
docker compose exec servei ordre Executa una ordre dins d'un servei
docker compose build Només construeix les imatges
docker compose pull Descarrega les versions noves de les imatges
docker compose stop / start Atura / arrenca sense eliminar
docker compose down [-v] Elimina contenidors i xarxa (i volums amb -v)
docker compose config Mostra el fitxer final amb les variables substituïdes (útil per detectar errors)

La referència completa és a la documentació de Docker Compose i a l'especificació del fitxer Compose.

Miniactivitat - De comandes a Compose

  1. Afegiu al compose.yaml de WordPress un servei phpmyadmin (el mateix de la miniactivitat del pas 7). Quantes línies té el fitxer, comparat amb el fitxer de comandes que vau escriure?
  2. Moveu totes les contrasenyes del WordPress a un fitxer .env i comproveu el resultat amb docker compose config.
  3. A l'aplicació de comentaris, elimineu el healthcheck i la condició del depends_on, feu docker compose down -v i docker compose up -d. Mireu els logs de web: hi ha errors de connexió en la primera arrencada? Per què?
  4. Aixequeu dues còpies de l'aplicació de comentaris amb docker compose -p copia1 up -d i docker compose -p copia2 up -d (canviant WEB_PORT). Quins noms tenen els contenidors, les xarxes i els volums?

9.6 Neteja

docker compose down -v      # a cada directori de projecte

Següent pas: ja teniu una aplicació pròpia funcionant amb Compose. Al Pas 10 veurem com fer imatges més petites i segures, com publicar-les a Docker Hub (el mateix lloc d'on descarregàvem imatges al pas 1) i com depurar problemes. Després, ja podeu fer la Pràctica 1.