resources/views/pages/frontend/docs/arquitectura-docker.md
Última modificación: 27 Oct, 2026

Arquitectura en Docker (Multi-Container & Zero Trust)

La implementación de Mañuti en Docker empaqueta toda la complejidad de la arquitectura distribuida dentro de un ecosistema de 10 contenedores orquestados con Docker Compose, divididos en 2 redes virtuales aisladas (frontend_net y backend_net) y respaldados por 4 volúmenes persistentes.

Gracias al diseño de "Golden Image" Polimórfica, los nodos de Aplicación (web01, web02, web03), el procesador de colas (worker) y el servidor de WebSockets (reverb) utilizan exactamente la misma imagen base compilada con PHP 8.5-FPM, Node.js 20 y Python 3 + Graphviz, diferenciando su función únicamente por el comando de arranque.


1. Diagrama de la Topología Dockerizada

flowchart TD
    subgraph Capa1 ["CAPA 1: ACCESO PÚBLICO (frontend_net)"]
        LB["Load Balancer Nginx (framedev_lb)<br/>:80 / :443"]
    end

    WEB["web01..web03<br/>(PHP 8.5-FPM)<br/>[Ambas Redes]"]
    WORKER["worker<br/>(Horizon)<br/>[backend_net]"]
    REVERB["reverb<br/>(WebSockets)<br/>[Ambas Redes]"]

    LB -->|"FastCGI :9000"| WEB
    LB -->|"FastCGI :9000"| WORKER
    LB -->|"Proxy WSS /app :8080"| REVERB

    subgraph Capa3 ["CAPA 3: PERSISTENCIA AISLADA (backend_net)"]
        direction LR
        MARIADB[("MariaDB<br/>:3306")]
        REDIS[("Redis<br/>:6379")]
        MONGODB[("MongoDB<br/>:27017")]
        MINIO[("MinIO<br/>:9000")]
    end

    %% Conexiones hacia la capa de persistencia
    WEB --> MARIADB
    WEB --> REDIS
    WEB --> MONGODB
    WEB --> MINIO

    WORKER --> MARIADB
    WORKER --> REDIS
    WORKER --> MONGODB
    WORKER --> MINIO
    
    REVERB --> REDIS

Estructura de Directorios del Proyecto Docker

manuti-app/
├── docker/
│   ├── nginx/
│   │   └── default.conf          # Upstream web_cluster (web01..03) y reverb_cluster
│   └── php/
│       ├── Dockerfile            # Golden Image (PHP 8.5 + Node 20 + Python 3 + Graphviz)
│       └── php.ini               # Tuning OPcache JIT (256M) y límites de carga
├── logs/                         # Logs mapeados (nginx, php, mysql, redis, mongo, minio)
├── src/                          # Código fuente de Mañuti (Laravel)
│   └── .env                      # Variables apuntando a nombres de servicio internos
├── .dockerignore
└── docker-compose.yml            # Orquestación de los 10 servicios, redes y volúmenes

2. Preparación del Host (Ubuntu LTS) e Instalación de Docker Engine

Ejecuta estos comandos en el servidor anfitrión limpio:

# 1. Actualizar el sistema e instalar prerrequisitos
sudo apt-get update && sudo apt-get upgrade -y
sudo apt-get install -y ca-certificates curl gnupg

# 2. Agregar la llave GPG oficial de Docker
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

# 3. Registrar el repositorio oficial de Docker
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# 4. Instalar Docker Engine y Docker Compose Plugin
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

# 5. Habilitar y verificar
sudo systemctl enable --now docker
sudo docker --version

3. La "Golden Image" (docker/php/Dockerfile)

Esta imagen incluye PHP 8.5-FPM, las extensiones obligatorias para Horizon (pcntl) y Reverb (sockets), los drivers PECL para Redis y MongoDB, Node.js 20 para Vite, y el entorno Python 3 + Graphviz + Fuentes Tipográficas (fonts-liberation y fontconfig) para que el generador de mapas SVG renderice los textos correctamente.

FROM php:8.5-fpm

# 1. DEPENDENCIAS DEL SISTEMA, NODE.JS 20 Y EXTENSIONES PHP CORE
RUN apt-get update && apt-get install -y \
    git curl zip unzip supervisor \
    libpng-dev libjpeg-dev libfreetype6-dev libonig-dev \
    libxml2-dev libzip-dev libicu-dev libcurl4-openssl-dev \
    && curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
    && apt-get install -y nodejs \
    && docker-php-ext-configure gd --with-freetype --with-jpeg \
    && docker-php-ext-install -j$(nproc) \
        pdo_mysql mbstring exif pcntl bcmath sockets gd zip soap intl \
    && apt-get clean && rm -rf /var/lib/apt/lists/*

# 2. MOTOR PYTHON 3 + GRAPHVIZ + FUENTES PARA MAPAS SVG
RUN apt-get update && apt-get install -y \
    python3 python3-pip graphviz fonts-liberation fontconfig \
    && fc-cache -f && dot -c \
    && python3 -m pip install --no-cache-dir --break-system-packages \
        graphviz pathspec pyparsing boto3 python-dotenv \
    && rm -rf /var/lib/apt/lists/*

# 3. DRIVERS PECL (REDIS & MONGODB)
RUN pecl install redis mongodb \
    && docker-php-ext-enable redis mongodb

# 4. COMPOSER
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer

# 5. DIRECTORIO DE TRABAJO Y PERMISOS (UID 1000)
WORKDIR /var/www/html
COPY ./src /var/www/html

RUN usermod -u 1000 www-data && groupmod -g 1000 www-data && \
    mkdir -p /var/www/.composer && \
    chown -R www-data:www-data /var/www /var/www/html && \
    chmod -R 775 /var/www/html/storage /var/www/html/bootstrap/cache

USER www-data
CMD ["php-fpm"]

4. Configuración de PHP para Producción (docker/php/php.ini)

memory_limit = 512M
max_execution_time = 300
upload_max_filesize = 100M
post_max_size = 100M

opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=20000
opcache.validate_timestamps=0
opcache.jit_buffer_size=100M
opcache.jit=1235

date.timezone = America/Mexico_City
expose_php = Off

5. Configuración del Balanceador Nginx (docker/nginx/default.conf)

# 1. CLUSTER DE NODOS WEB (PHP-FPM sobre puerto 9000)
upstream web_cluster {
    server web01:9000;
    server web02:9000;
    server web03:9000;
    keepalive 32;
}

# 2. CLUSTER DE REVERB (WEBSOCKETS sobre puerto 8080)
upstream reverb_cluster {
    server reverb:8080;
}

server {
    listen 80;
    server_name localhost;
    root /var/www/html/public;
    index index.php index.html;

    error_log  /var/log/nginx/error.log warn;
    access_log /var/log/nginx/access.log;

    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;

    # A) TRÁFICO HTTP NORMAL
    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    # B) TÚNEL WEBSOCKETS (/app -> Reverb)
    location ~ ^/app/ {
        proxy_pass http://reverb_cluster;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 86400s;
        proxy_send_timeout 86400s;
    }

    # C) BALANCEO FASTCGI HACIA LOS 3 NODOS PHP
    location ~ \.php$ {
        fastcgi_pass web_cluster;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
        fastcgi_buffers 16 16k;
        fastcgi_buffer_size 32k;
        fastcgi_read_timeout 300s;
    }

    location ~ /\.(?!well-known).* {
        deny all;
    }
}

6. Archivo Maestro docker-compose.yml

Define los 10 contenedores, sus healthchecks y la segmentación en dos subredes (172.20.0.0/16 y 172.21.0.0/16):

services:
  # 1. LOAD BALANCER (Nginx)
  lb:
    image: nginx:alpine
    container_name: framedev_lb
    restart: unless-stopped
    ports:
      - "80:80"
    volumes:
      - ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
      - ./src:/var/www/html:ro
      - ./logs/nginx:/var/log/nginx
    depends_on:
      - web01
      - web02
      - web03
      - reverb
    networks:
      - frontend_net

  # 2. NODOS WEB (01, 02, 03)
  web01:
    image: ghcr.io/activemediacdmx/manuti:latest
    build:
      context: .
      dockerfile: ./docker/php/Dockerfile
    container_name: framedev_web01
    restart: unless-stopped
    working_dir: /var/www/html
    volumes:
      - ./src:/var/www/html
      - ./docker/php/php.ini:/usr/local/etc/php/conf.d/custom.ini
      - ./logs/php:/var/log/php
    environment: &app_env
      APP_ENV: production
      APP_DEBUG: "false"
      DB_HOST: mariadb
      DB_PORT: 3306
      DB_DATABASE: framedev_db
      DB_USERNAME: framedev
      DB_PASSWORD: kls2qr824
      REDIS_HOST: redis
      REDIS_PORT: 6379
      MONGODB_DSN: "mongodb://mongo:27017/framedev_logs"
      AWS_ACCESS_KEY_ID: minioadmin
      AWS_SECRET_ACCESS_KEY: tCT8H83o7U7Gm2
      AWS_ENDPOINT: http://minio:9000
      AWS_BUCKET: framedev-bucket
      REVERB_HOST: reverb
      REVERB_PORT: 8080
      REVERB_SCHEME: http
    networks:
      - frontend_net
      - backend_net
    expose:
      - "9000"

  web02:
    image: ghcr.io/activemediacdmx/manuti:latest
    container_name: framedev_web02
    restart: unless-stopped
    working_dir: /var/www/html
    volumes:
      - ./src:/var/www/html
      - ./docker/php/php.ini:/usr/local/etc/php/conf.d/custom.ini
    environment: *app_env
    networks:
      - frontend_net
      - backend_net
    expose:
      - "9000"

  web03:
    image: ghcr.io/activemediacdmx/manuti:latest
    container_name: framedev_web03
    restart: unless-stopped
    working_dir: /var/www/html
    volumes:
      - ./src:/var/www/html
      - ./docker/php/php.ini:/usr/local/etc/php/conf.d/custom.ini
    environment: *app_env
    networks:
      - frontend_net
      - backend_net
    expose:
      - "9000"

  # 5. REVERB (WebSockets)
  reverb:
    image: ghcr.io/activemediacdmx/manuti:latest
    container_name: framedev_reverb
    restart: unless-stopped
    working_dir: /var/www/html
    command: php artisan reverb:start --host=0.0.0.0 --port=8080
    volumes:
      - ./src:/var/www/html
    environment: *app_env
    expose:
      - "8080"
    networks:
      - frontend_net
      - backend_net
    depends_on:
      - redis

  # 6. HORIZON (Queue Worker)
  worker:
    image: ghcr.io/activemediacdmx/manuti:latest
    container_name: framedev_worker
    restart: unless-stopped
    working_dir: /var/www/html
    command: php artisan horizon
    volumes:
      - ./src:/var/www/html
    environment: *app_env
    networks:
      - backend_net
    depends_on:
      - redis
      - mariadb

  # 7. MARIADB
  mariadb:
    image: mariadb:11.2
    container_name: framedev_db
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: framedev_db
      MYSQL_USER: framedev
      MYSQL_PASSWORD: kls2qr824
    volumes:
      - mariadb_data:/var/lib/mysql
    ports:
      - "3307:3306"
    networks:
      - backend_net

  # 8. REDIS
  redis:
    image: redis:alpine
    container_name: framedev_redis
    restart: always
    command: redis-server --appendonly yes
    volumes:
      - redis_data:/data
    ports:
      - "6378:6379"
    networks:
      - backend_net

  # 9. MONGODB
  mongo:
    image: mongo:latest
    container_name: framedev_mongo
    restart: always
    environment:
      MONGO_INITDB_DATABASE: framedev_logs
    volumes:
      - mongo_data:/data/db
    ports:
      - "27018:27017"
    networks:
      - backend_net

  # 10. MINIO (S3 Object Storage)
  minio:
    image: minio/minio
    container_name: framedev_minio
    restart: always
    command: server /data --console-address ":9001"
    environment:
      MINIO_ROOT_USER: minioadmin
      MINIO_ROOT_PASSWORD: tCT8H83o7U7Gm2
    volumes:
      - minio_data:/data
    ports:
      - "9000:9000"
      - "9001:9001"
    networks:
      - backend_net

networks:
  frontend_net:
    driver: bridge
  backend_net:
    driver: bridge

volumes:
  mariadb_data:
  redis_data:
  mongo_data:
  minio_data:

7. Despliegue desde GitHub Container Registry (GHCR) e Inicialización

7.1. Autenticación en ghcr.io y Extracción de ./src

# 1. Iniciar sesión con tu Personal Access Token (PAT)
echo "TU_PAT_DE_GITHUB" | sudo docker login ghcr.io -u manuelemiliano --password-stdin

# 2. Clonar la estructura de orquestación
git clone https://github.com/activemediacdmx/Manuti.git manuti-app
cd manuti-app

# 3. Extraer el código compilado desde la imagen oficial hacia ./src
sudo docker pull ghcr.io/activemediacdmx/manuti:latest
id=$(sudo docker create ghcr.io/activemediacdmx/manuti:latest)
sudo docker cp $id:/var/www/html ./src
sudo docker rm -v $id
sudo chown -R 1000:1000 src

7.2. Encendido del Stack y Configuración de MinIO / Migraciones

# 1. Crear carpetas de logs y levantar contenedores
mkdir -p logs/{nginx,mysql,redis,mongo,minio,php}
sudo chmod -R 777 logs
sudo docker compose up -d

# 2. Generar APP_KEY y ejecutar Migraciones + Seeders
sudo docker compose exec -u www-data web01 php artisan key:generate
sudo docker compose exec -u www-data web01 php artisan migrate --seed --force

# 3. Inicializar Bucket y políticas públicas en MinIO (usando contenedor efímero mc)
alias dmc='sudo docker run --rm -it --network manuti-app_backend_net minio/mc'
dmc alias set miframe http://minio:9000 minioadmin tCT8H83o7U7Gm2
dmc mb miframe/framedev-bucket
dmc anonymous set download miframe/framedev-bucket/avatars
dmc anonymous set download miframe/framedev-bucket/architecture

# 4. Compilar assets con la IP del host y limpiar caché en los 3 nodos web
sudo docker exec -u www-data framedev_web01 npm run build
for node in framedev_web01 framedev_web02 framedev_web03; do
    sudo docker exec -u www-data $node php artisan optimize:clear
done
sudo docker compose exec -u www-data web01 php artisan horizon:terminate

8. Escalado Horizontal en Caliente y Creación de Imagen Hermética

8.1. Agregar un 4to Nodo Web (web04) sin Downtime

  1. Duplica el bloque web03 como web04 en docker-compose.yml.
  2. Agrega server web04:9000; al bloque upstream web_cluster en docker/nginx/default.conf.
  3. Ejecuta:
    sudo docker compose up -d
    sudo docker compose exec lb nginx -s reload
    

8.2. Generar y Publicar una Imagen Hermética (Sin Volúmenes Externos)

Una vez validado el contenedor web01 con todas sus dependencias y assets compilados:

# 1. Inyectar carpeta src actualizada dentro del contenedor y hacer commit
sudo docker cp ./src/. framedev_web01:/var/www/html/
sudo docker commit framedev_web01 ghcr.io/activemediacdmx/manuti:v12-hermetica-final

# 2. Estrategia de Doble Tag (Versión + Latest) y Push al Registry
sudo docker tag ghcr.io/activemediacdmx/manuti:v12-hermetica-final ghcr.io/activemediacdmx/manuti:latest
sudo docker push ghcr.io/activemediacdmx/manuti:v12-hermetica-final
sudo docker push ghcr.io/activemediacdmx/manuti:latest
Edita este documento en >> arquitectura-docker.md
← Volver a la Landing Page