Salta el contingut

Docker Compose

Docker Compose: gestionant aplicacions multi-contenidor

Fins ara hem treballat amb contenidors individuals, però les aplicacions reals sovint estan compostes de múltiples serveis que treballen junts. Per exemple, una aplicació web típica podria tenir un contenidor per l'aplicació, un altre per la base de dades, i potser un altre per un sistema de caching com Redis. Gestionar tots aquests contenidors manualment, amb totes les seves opcions i dependències, es fa ràpidament complicat. Aquí és on Docker Compose resulta invaluable.

Què és Docker Compose i per què és tan útil

Docker Compose és una eina per definir i executar aplicacions Docker multi-contenidor. En lloc d'haver de recordar comandes llargues amb dotzenes de paràmetres per cada contenidor, definiu tot en un fitxer YAML anomenat docker-compose.yml. Després, amb una sola comanda, podeu arrencar tots els serveis alhora, i Docker Compose s'encarrega de crear els volums, les xarxes, i els contenidors necessaris.

Però Docker Compose és més que això: també gestiona les dependències entre serveis. Si l'aplicació web necessita que la base de dades estigui funcionant primer, podeu especificar-ho, i Docker Compose arrencarà els serveis en l'ordre correcte. També facilita veure els logs de tots els serveis alhora, aturar-los tots amb una comanda, i reconstruir-los quan feu canvis.

Creant el primer docker-compose.yml

Anem a crear un exemple pràctic que combini una aplicació web amb una base de dades. Crearem una aplicació senzilla que emmagatzema i mostra comentaris. Primer, necessitem una aplicació. Aquí teniu un exemple molt bàsic amb Flask i PostgreSQL.

Creeu un directori nou per aquest projecte:

mkdir app-amb-db
cd app-amb-db

Creeu l'aplicació app.py:

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

app = Flask(__name__)

def get_db_connection():
    conn = psycopg2.connect(
        host=os.environ.get('DB_HOST', 'db'),
        database=os.environ.get('DB_NAME', 'comentaris'),
        user=os.environ.get('DB_USER', 'usuari'),
        password=os.environ.get('DB_PASSWORD', 'contrasenya')
    )
    return conn

@app.route('/', methods=['GET', 'POST'])
def index():
    if request.method == 'POST':
        comentari = request.form['comentari']
        conn = get_db_connection()
        cur = conn.cursor()
        cur.execute('INSERT INTO comentaris (text) VALUES (%s)', (comentari,))
        conn.commit()
        cur.close()
        conn.close()

    conn = get_db_connection()
    cur = conn.cursor()
    cur.execute('SELECT * FROM comentaris ORDER BY id DESC')
    comentaris = cur.fetchall()
    cur.close()
    conn.close()

    return render_template_string('''
        <h1>Aplicació de Comentaris</h1>
        <form method="post">
            <textarea name="comentari" rows="3" cols="50"></textarea><br>
            <input type="submit" value="Afegir comentari">
        </form>
        <h2>Comentaris:</h2>
        <ul>
        {% for comentari in comentaris %}
            <li>{{ comentari[1] }}</li>
        {% endfor %}
        </ul>
    ''', comentaris=comentaris)

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

Creeu el fitxer requirements.txt:

Flask==3.1.0
psycopg2-binary==2.9.10

Creeu un script per inicialitzar la base de dades, init.sql:

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

Creeu el Dockerfile per l'aplicació:

FROM python:3.12-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

CMD ["python", "app.py"]

I ara, la part més important, el fitxer docker-compose.yml:

services:
  web:
    build: .
    ports:
      - "5000:5000"
    environment:
      - DB_HOST=db
      - DB_NAME=comentaris
      - DB_USER=usuari
      - DB_PASSWORD=contrasenya_segura
    depends_on:
      - db
    volumes:
      - .:/app

  db:
    image: postgres:17-alpine
    environment:
      - POSTGRES_DB=comentaris
      - POSTGRES_USER=usuari
      - POSTGRES_PASSWORD=contrasenya_segura
    volumes:
      - dades-postgres:/var/lib/postgresql/data
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql

volumes:
  dades-postgres:

Aquest fitxer defineix una aplicació completa amb dos serveis: "web" i "db". Anem a analitzar cada secció detingudament perquè és important entendre com funciona.

La secció services defineix cada contenidor que forma part de l'aplicació. Tenim dos serveis: "web" i "db".

Per al servei "web", en lloc d'especificar una imatge existent, utilitzem build: ., que li diu a Docker Compose que construeixi la imatge a partir del Dockerfile que hi ha al directori actual. La secció ports mapeja el port 5000 del nostre ordinador al port 5000 del contenidor.

La secció environment defineix variables d'entorn que estarà disponibles dins del contenidor. Fixeu-vos que DB_HOST=db: això funciona perquè Docker Compose automàticament crea una xarxa on els serveis poden comunicar-se entre ells usant els seus noms com a hostnames. Així, des del contenidor "web", podem accedir a la base de dades simplement referint-nos a "db".

El paràmetre depends_on indica que el servei "web" depèn del servei "db". Docker Compose s'assegurarà que la base de dades s'arrenqui abans que l'aplicació web. Això no garanteix que la base de dades estigui LLESTA (només que el contenidor s'hagi iniciat), però és un primer pas.

El volum .:/app munta el directori actual dins de /app al contenidor, permetent-nos desenvolupar i veure els canvis sense reconstruir.

Per al servei "db", utilitzem la imatge oficial de PostgreSQL. Les variables d'entorn configuren el nom de la base de dades, l'usuari i la contrasenya. El primer volum dades-postgres:/var/lib/postgresql/data assegura que les dades de la base de dades persisteixin entre reinicios. El segon volum munta el nostre script SQL d'inicialització a una ubicació especial: la imatge de PostgreSQL automàticament executa qualsevol script .sql que trobi a /docker-entrypoint-initdb.d/ quan crea la base de dades per primera vegada.

Finalment, la secció volumes al final del fitxer declara els volums anomenats que utilitzem. Docker Compose crearà automàticament el volum "dades-postgres" si no existeix.

Executant l'aplicació amb Docker Compose

Amb tot això configurat, executar l'aplicació és extremadament senzill:

docker-compose up

Aquesta comanda fa TOTES aquestes coses: 1. Crea la xarxa necessària per que els serveis es puguin comunicar 2. Crea el volum "dades-postgres" si no existeix 3. Construeix la imatge per al servei "web" si no està construïda 4. Descarrega la imatge de PostgreSQL si no la teniu 5. Arrenca el contenidor de la base de dades 6. Espera que la base de dades estigui llesta 7. Arrenca el contenidor de l'aplicació web 8. Us mostra els logs de tots els serveis en temps real

Veureu els logs de tots dos serveis barrejats a la pantalla, cadascun amb un prefix de color que indica de quin servei provenen. Podeu veure com PostgreSQL s'inicialitza, executa el script SQL per crear la taula, i després l'aplicació Flask s'arrenca i es connecta a la base de dades.

Obriu el navegador i aneu a http://localhost:5000. Hauríeu de veure l'aplicació funcionant! Podeu afegir comentaris i es guardaran a la base de dades PostgreSQL que està funcionant en un contenidor separat.

Si voleu executar-ho en segon pla (detached mode), utilitzeu:

docker-compose up -d

Per veure els logs després:

docker-compose logs

O per seguir els logs en temps real:

docker-compose logs -f

Per aturar tots els serveis:

docker-compose down

Això atura i elimina tots els contenidors i la xarxa, però manté els volums (i per tant les dades de la base de dades). Si també voleu eliminar els volums:

docker-compose down -v

Per reconstruir les imatges si heu fet canvis:

docker-compose build

O per reconstruir i arrencar en una sola comanda:

docker-compose up --build

La documentació completa de Docker Compose està a https://docs.docker.com/compose/